15/ Refactor flicker package

Flicker no longer contains only wm and sf traces and flicker subjects.

It now contains parsers, subjects, traces, flicker detection.

Since not everything belonged to WM packages, the old package structure was causing confusion. Moreover, it was not flexible enough to move part of the codebase to winscope

The new package structure is divider as follows:

android/tools
-- common -> compiles to JS and shared with winscope (build as flickerlib-common)
-- device -> android device dependent
---- apphelpers -> app helpers for flicker tests (build as flickerlib-apphelpers)
---- helpers -> helper utils for other projects, such as wmsmoketests (build as flickerlib-helpers)
---- traces -> parsers and monitors (build as flickerlib-parsers)
---- flicker -> flicker detection functionality

Also add JsExport annotation for KotlinJs and address errors to migrate to new IR compiler (other is deprecated). Limitations include:
- no support for inner exported classes
- name mangling
- JsExport annotation
- JsName when multiple constructors exist

Bug: 262369733
Test: atest FlickerLibTest
Change-Id: I3c94049cd4121ab91422e2a014db9f159693c29d
diff --git a/libraries/flicker/Android.bp b/libraries/flicker/Android.bp
index fc6ca3e..eb76f92 100644
--- a/libraries/flicker/Android.bp
+++ b/libraries/flicker/Android.bp
@@ -21,55 +21,6 @@
 java_test {
     name: "flickerlib",
     platform_apis: true,
-    optimize: {
-        enabled: false
-    },
-    srcs: [
-        "src/com/android/server/wm/flicker/**/*.java",
-        "src/com/android/server/wm/flicker/**/*.kt",
-        "src/com/android/server/wm/flicker/**/*.eventlog"
-    ],
-    java_resource_dirs: [
-        "src/com/android/server/wm/flicker/service/resources/"
-    ],
-    static_libs: [
-        "flickerlib-helpers",
-        "compatibility-device-util-axt",
-        "ub-uiautomator",
-        "androidx.test.uiautomator_uiautomator",
-        "androidx.test.ext.junit",
-        "truth",
-        "launcher-helper-lib",
-        "wm-proto-parsers",
-        "platform-test-annotations",
-        "platform-test-core-rules",
-        "health-testing-utils",
-        "collector-device-lib",
-    ],
-}
-
-java_library {
-    name: "flickerlib-helpers",
-    sdk_version: "test_current",
-    optimize: {
-        enabled: false
-    },
-    srcs: [
-        "src/**/helpers/*.java",
-        "src/**/helpers/*.kt",
-    ],
-    static_libs: [
-        "compatibility-device-util-axt",
-        "app-helpers-core",
-        "launcher-helper-lib",
-        "wm-proto-parsers",
-        "launcher-aosp-tapl",
-    ],
-}
-
-java_library {
-    name: "wm-proto-parsers",
-    sdk_version: "test_current",
     kotlincflags: [
         "-Xmulti-platform",
         "-opt-in=kotlin.ExperimentalMultiplatform",
@@ -78,27 +29,95 @@
         enabled: false
     },
     srcs: [
-        "src/com/android/server/wm/traces/parser/**/*.java",
-        "src/com/android/server/wm/traces/parser/**/*.kt",
+        "src/android/tools/device/flicker/**/*.kt",
+        "src/android/tools/device/flicker/**/*.eventlog"
     ],
     common_srcs: [
-        "src/com/android/server/wm/traces/common/**/*.kt",
+        "src/android/tools/common/**/*.kt",
     ],
     static_libs: [
-        "android-support-annotations",
+        "flickerlib-apphelpers",
+        "flickerlib-helpers",
+        "flickerlib-parsers",
+        "compatibility-device-util-axt",
+        "androidx.test.uiautomator_uiautomator",
         "androidx.test.ext.junit",
-        "platformprotosnano",
-        "layersprotoslite",
-        "flicker-tags-proto",
+        "launcher-helper-lib",
+        "platform-test-annotations",
+        "platform-test-core-rules",
+        "health-testing-utils",
+        "collector-device-lib",
     ],
 }
 
 java_library {
-    name: "flicker-tags-proto",
+    name: "flickerlib-apphelpers",
+    platform_apis: true,
+    optimize: {
+        enabled: false
+    },
     srcs: [
-        "**/*.proto",
+        "src/android/tools/device/apphelpers/*.kt",
+    ],
+    static_libs: [
+        "flickerlib-parsers",
+        "compatibility-device-util-axt",
+        "app-helpers-core",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
+
+java_library {
+    name: "flickerlib-helpers",
+    platform_apis: true,
+    optimize: {
+        enabled: false
+    },
+    srcs: [
+        "src/android/tools/device/helpers/*.kt",
+    ],
+    static_libs: [
+        "flickerlib-parsers",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+}
+
+java_library {
+    name: "flickerlib-parsers",
+    platform_apis: true,
+    kotlincflags: [
+        "-Xmulti-platform",
+        "-opt-in=kotlin.ExperimentalMultiplatform",
     ],
     optimize: {
         enabled: false
-    }
+    },
+    srcs: [
+        "src/android/tools/device/traces/**/*.kt",
+    ],
+    common_srcs: [
+        "src/android/tools/common/**/*.kt",
+    ],
+    static_libs: [
+        "flickerlib-common",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.runner",
+        "platformprotosnano",
+        "layersprotoslite",
+    ],
+}
+
+java_library {
+    name: "flickerlib-common",
+    kotlincflags: [
+        "-Xmulti-platform",
+        "-opt-in=kotlin.ExperimentalMultiplatform",
+    ],
+    optimize: {
+        enabled: false
+    },
+    common_srcs: [
+        "src/android/tools/common/**/*.kt",
+    ],
 }
diff --git a/libraries/flicker/src/android/tools/common/Cache.kt b/libraries/flicker/src/android/tools/common/Cache.kt
new file mode 100644
index 0000000..07beae6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Cache.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import kotlin.js.JsName
+
+object Cache {
+    private val cache = mutableMapOf<Any, Any>()
+
+    @JsName("get")
+    fun <T : Any> get(element: T): T {
+        return Cache.cache.getOrPut(element) { element } as T
+    }
+
+    @JsName("clear")
+    fun clear() {
+        Cache.cache.clear()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/Consts.kt b/libraries/flicker/src/android/tools/common/Consts.kt
new file mode 100644
index 0000000..54b0987
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Consts.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+const val FLICKER_TAG = "FLICKER"
+const val MILLISECOND_AS_NANOSECONDS: Long = 1000000
+const val SECOND_AS_NANOSECONDS: Long = 1000000000
+const val MINUTE_AS_NANOSECONDS: Long = 60000000000
+const val HOUR_AS_NANOSECONDS: Long = 3600000000000
+const val DAY_AS_NANOSECONDS: Long = 86400000000000
diff --git a/libraries/flicker/src/android/tools/common/CrossPlatform.kt b/libraries/flicker/src/android/tools/common/CrossPlatform.kt
new file mode 100644
index 0000000..ad20880
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/CrossPlatform.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+object CrossPlatform {
+    var log: ILogger = LoggerBuilder().build()
+        private set
+    var timestamp: TimestampFactory = TimestampFactory()
+        private set
+
+    fun setLogger(logger: ILogger) = apply { log = logger }
+    fun setTimestampFactory(factory: TimestampFactory) = apply { timestamp = factory }
+}
diff --git a/libraries/flicker/src/android/tools/common/Extensions.kt b/libraries/flicker/src/android/tools/common/Extensions.kt
new file mode 100644
index 0000000..1aa60e4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Extensions.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+import kotlin.js.JsName
+
+@JsName("withCache")
+inline fun <reified T : Any> withCache(newInstancePredicate: () -> T): T =
+    Cache.get(newInstancePredicate())
diff --git a/libraries/flicker/src/android/tools/common/FloatFormatter.kt b/libraries/flicker/src/android/tools/common/FloatFormatter.kt
new file mode 100644
index 0000000..0117625
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/FloatFormatter.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+import kotlin.js.JsName
+
+/**
+ * A formatter to print floats with up to 3 decimal digits.
+ *
+ * This is necessary because multiplatform kotlin projects don't support String.format yet (issue
+ * KT-21644)
+ */
+object FloatFormatter {
+    @JsName("format")
+    fun format(value: Float): String {
+        return ((value * 1000).toInt() / 1000.0).toString()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/ILogger.kt b/libraries/flicker/src/android/tools/common/ILogger.kt
new file mode 100644
index 0000000..0a8f836
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/ILogger.kt
@@ -0,0 +1,26 @@
+/*
+ * 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
+
+interface ILogger {
+    fun <T> withTracing(name: String, predicate: () -> T): T
+    fun v(tag: String, msg: String)
+    fun d(tag: String, msg: String)
+    fun i(tag: String, msg: String)
+    fun w(tag: String, msg: String)
+    fun e(tag: String, msg: String, error: Throwable? = null)
+}
diff --git a/libraries/flicker/src/android/tools/common/IScenario.kt b/libraries/flicker/src/android/tools/common/IScenario.kt
new file mode 100644
index 0000000..22e839b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/IScenario.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+interface IScenario {
+    val description: String
+    val key: String
+    val isEmpty: Boolean
+
+    val startRotation: Rotation
+    val endRotation: Rotation
+    val navBarMode: NavBar
+}
diff --git a/libraries/flicker/src/android/tools/common/ITrace.kt b/libraries/flicker/src/android/tools/common/ITrace.kt
new file mode 100644
index 0000000..ffcec9c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/ITrace.kt
@@ -0,0 +1,51 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+interface ITrace<Entry : ITraceEntry> {
+    @JsName("entries") val entries: Array<Entry>
+
+    @JsName("slice") fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ITrace<Entry>
+
+    /**
+     * @return an entry that matches exactly [timestamp]
+     * @throws if there is no entry in the trace at [timestamp]
+     */
+    @JsName("getEntryExactlyAt")
+    fun getEntryExactlyAt(timestamp: Timestamp): Entry {
+        return entries.firstOrNull { it.timestamp == timestamp }
+            ?: throw RuntimeException("Entry does not exist for timestamp $timestamp")
+    }
+
+    /**
+     * @return the entry that is "active' at [timestamp]
+     * ```
+     *         (the entry at [timestamp] or the one before it if no entry exists at [timestamp])
+     * @throws if
+     * ```
+     * the provided [timestamp] is before all entries in the trace
+     */
+    @JsName("getEntryAt")
+    fun getEntryAt(timestamp: Timestamp): Entry {
+        return entries.dropLastWhile { it.timestamp > timestamp }.lastOrNull()
+            ?: error("No entry at or before timestamp $timestamp")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/ITraceEntry.kt b/libraries/flicker/src/android/tools/common/ITraceEntry.kt
new file mode 100644
index 0000000..bf8bfcf
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/ITraceEntry.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/** Common interface for Layer and WindowManager trace entries. */
+@JsExport
+interface ITraceEntry {
+    /** @return timestamp of current entry */
+    @JsName("timestamp") val timestamp: Timestamp
+}
diff --git a/libraries/flicker/src/android/tools/common/LoggerBuilder.kt b/libraries/flicker/src/android/tools/common/LoggerBuilder.kt
new file mode 100644
index 0000000..162b3c1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/LoggerBuilder.kt
@@ -0,0 +1,80 @@
+/*
+ * 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
+
+class LoggerBuilder {
+    private var onV: (tag: String, msg: String) -> Unit = { tag, msg -> println("(V) $tag $msg") }
+    private var onD: (tag: String, msg: String) -> Unit = { tag, msg -> println("(D) $tag $msg") }
+    private var onI: (tag: String, msg: String) -> Unit = { tag, msg -> println("(I) $tag $msg") }
+    private var onW: (tag: String, msg: String) -> Unit = { tag, msg -> println("(W) $tag $msg") }
+    private var onE: (tag: String, msg: String, error: Throwable?) -> Unit = { tag, msg, error ->
+        println("(e) $tag $msg $error")
+        error?.printStackTrace()
+    }
+    private var onTracing: (name: String, predicate: () -> Any) -> Any = { name, predicate ->
+        try {
+            println("(withTracing#start) $name")
+            predicate()
+        } finally {
+            println("(withTracing#end) $name")
+        }
+    }
+
+    fun setV(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
+        onV = predicate
+    }
+
+    fun setD(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
+        onD = predicate
+    }
+
+    fun setI(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
+        onI = predicate
+    }
+
+    fun setW(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
+        onW = predicate
+    }
+
+    fun setE(predicate: (tag: String, msg: String, error: Throwable?) -> Unit): LoggerBuilder =
+        apply {
+            onE = predicate
+        }
+
+    fun setOnTracing(predicate: (name: String, predicate: () -> Any) -> Any): LoggerBuilder =
+        apply {
+            onTracing = predicate
+        }
+
+    fun build(): ILogger {
+        return object : ILogger {
+            override fun d(tag: String, msg: String) = onD(tag, msg)
+
+            override fun e(tag: String, msg: String, error: Throwable?) = onE(tag, msg, error)
+
+            override fun i(tag: String, msg: String) = onI(tag, msg)
+
+            override fun v(tag: String, msg: String) = onV(tag, msg)
+
+            override fun w(tag: String, msg: String) = onW(tag, msg)
+
+            override fun <T> withTracing(name: String, predicate: () -> T): T {
+                return onTracing(name, predicate as () -> Any) as T
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/NavBar.kt b/libraries/flicker/src/android/tools/common/NavBar.kt
new file mode 100644
index 0000000..f37d226
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/NavBar.kt
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+
+@JsExport
+enum class NavBar(val description: String, val value: String) {
+    MODE_3BUTTON("3_BUTTON_NAV", PlatformConsts.MODE_3BUTTON),
+    MODE_GESTURAL("GESTURAL_NAV", PlatformConsts.MODE_GESTURAL);
+
+    companion object {
+        fun getByValue(value: String) {
+            when (value) {
+                PlatformConsts.MODE_3BUTTON -> MODE_3BUTTON
+                PlatformConsts.MODE_GESTURAL -> MODE_GESTURAL
+                else -> error("Unknown nav bar mode $value")
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/PlatformConsts.kt b/libraries/flicker/src/android/tools/common/PlatformConsts.kt
new file mode 100644
index 0000000..fa30b3b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/PlatformConsts.kt
@@ -0,0 +1,93 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+object PlatformConsts {
+    /**
+     * The default Display id, which is the id of the primary display assuming there is one.
+     *
+     * Duplicated from [Display.DEFAULT_DISPLAY] because this class is used by JVM and KotlinJS
+     */
+    @JsName("DEFAULT_DISPLAY") const val DEFAULT_DISPLAY = 0
+
+    /**
+     * Window type: an application window that serves as the "base" window of the overall
+     * application
+     *
+     * Duplicated from [WindowManager.LayoutParams.TYPE_BASE_APPLICATION] because this class is used
+     * by JVM and KotlinJS
+     */
+    @JsName("TYPE_BASE_APPLICATION") const val TYPE_BASE_APPLICATION = 1
+
+    /**
+     * Window type: special application window that is displayed while the application is starting
+     *
+     * Duplicated from [WindowManager.LayoutParams.TYPE_APPLICATION_STARTING] because this class is
+     * used by JVM and KotlinJS
+     */
+    @JsName("TYPE_APPLICATION_STARTING") const val TYPE_APPLICATION_STARTING = 3
+
+    /**
+     * Rotation constant: 0 degrees rotation (natural orientation)
+     *
+     * Duplicated from [Surface.ROTATION_0] because this class is used by JVM and KotlinJS
+     */
+    @JsName("ROTATION_0") const val ROTATION_0 = 0
+
+    /**
+     * Rotation constant: 90 degrees rotation.
+     *
+     * Duplicated from [Surface.ROTATION_90] because this class is used by JVM and KotlinJS
+     */
+    @JsName("ROTATION_90") const val ROTATION_90 = 1
+
+    /**
+     * Rotation constant: 180 degrees rotation.
+     *
+     * Duplicated from [Surface.ROTATION_180] because this class is used by JVM and KotlinJS
+     */
+    @JsName("ROTATION_180") const val ROTATION_180 = 2
+
+    /**
+     * Rotation constant: 270 degrees rotation.
+     *
+     * Duplicated from [Surface.ROTATION_270] because this class is used by JVM and KotlinJS
+     */
+    @JsName("ROTATION_270") const val ROTATION_270 = 3
+
+    /**
+     * Navigation bar mode constant: 3 button navigation.
+     *
+     * Duplicated from [WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY] because this
+     * class is used by JVM and KotlinJS
+     */
+    @JsName("MODE_GESTURAL")
+    const val MODE_GESTURAL = "com.android.internal.systemui.navbar.gestural"
+
+    /**
+     * Navigation bar mode : gestural navigation.
+     *
+     * Duplicated from [WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY] because this
+     * class is used by JVM and KotlinJS
+     */
+    @JsName("MODE_3BUTTON")
+    const val MODE_3BUTTON = "com.android.internal.systemui.navbar.threebutton"
+}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/README.md b/libraries/flicker/src/android/tools/common/README.md
similarity index 100%
rename from libraries/flicker/src/com/android/server/wm/traces/common/README.md
rename to libraries/flicker/src/android/tools/common/README.md
diff --git a/libraries/flicker/src/android/tools/common/Rotation.kt b/libraries/flicker/src/android/tools/common/Rotation.kt
new file mode 100644
index 0000000..a81d01c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Rotation.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+
+@JsExport
+enum class Rotation(val description: String, val value: Int) {
+    ROTATION_0("ROTATION_0", PlatformConsts.ROTATION_0),
+    ROTATION_90("ROTATION_90", PlatformConsts.ROTATION_90),
+    ROTATION_180("ROTATION_180", PlatformConsts.ROTATION_180),
+    ROTATION_270("ROTATION_270", PlatformConsts.ROTATION_270);
+
+    fun isRotated() = this == ROTATION_90 || this == ROTATION_270
+
+    companion object {
+        private val VALUES = values()
+        fun getByValue(value: Int) = if (value == -1) ROTATION_0 else VALUES[value]
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/Scenario.kt b/libraries/flicker/src/android/tools/common/Scenario.kt
new file mode 100644
index 0000000..975ffa5
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Scenario.kt
@@ -0,0 +1,92 @@
+/*
+ * 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
+
+/**
+ * Legacy flicker test scenario
+ *
+ * @param testClass
+ * @param startRotation Initial screen rotation
+ * @param endRotation Final screen rotation
+ * @param navBarMode Navigation mode, such as 3 button or gestural.
+ * @param _extraConfig Additional configurations
+ *
+ * Defaults to [startRotation]
+ */
+class Scenario
+internal constructor(
+    val testClass: String,
+    override val startRotation: Rotation,
+    override val endRotation: Rotation,
+    override val navBarMode: NavBar,
+    _extraConfig: Map<String, Any?>,
+    override val description: String
+) : IScenario {
+    internal val extraConfig = _extraConfig.toMutableMap()
+
+    override val isEmpty = testClass.isEmpty()
+
+    override val key = if (isEmpty) "empty" else "${testClass}_$description"
+
+    /** If the initial screen rotation is 90 (landscape) or 180 (seascape) degrees */
+    val isLandscapeOrSeascapeAtStart: Boolean =
+        startRotation == Rotation.ROTATION_90 || startRotation == Rotation.ROTATION_270
+
+    val isGesturalNavigation = navBarMode == NavBar.MODE_GESTURAL
+
+    val isTablet: Boolean
+        get() =
+            extraConfig[IS_TABLET] as Boolean?
+                ?: error("$IS_TABLET property not initialized. Use [setIsTablet] to initialize ")
+
+    fun setIsTablet(isTablet: Boolean) {
+        extraConfig[IS_TABLET] = isTablet
+    }
+
+    fun <T> getConfigValue(key: String): T? = extraConfig[key] as T?
+
+    override fun toString(): String = key
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Scenario) return false
+
+        if (testClass != other.testClass) return false
+        if (startRotation != other.startRotation) return false
+        if (endRotation != other.endRotation) return false
+        if (navBarMode != other.navBarMode) return false
+        if (description != other.description) return false
+        if (extraConfig != other.extraConfig) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = testClass.hashCode()
+        result = 31 * result + startRotation.hashCode()
+        result = 31 * result + endRotation.hashCode()
+        result = 31 * result + navBarMode.hashCode()
+        result = 31 * result + description.hashCode()
+        result = 31 * result + extraConfig.hashCode()
+        return result
+    }
+
+    companion object {
+        internal const val IS_TABLET = "isTablet"
+        const val FAAS_BLOCKING = "faas:blocking"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/ScenarioBuilder.kt b/libraries/flicker/src/android/tools/common/ScenarioBuilder.kt
new file mode 100644
index 0000000..64585d1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/ScenarioBuilder.kt
@@ -0,0 +1,93 @@
+/*
+ * 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
+
+/** Helper class to create [Scenario]s */
+class ScenarioBuilder {
+    private var testClass: String = ""
+    private var startRotation = DEFAULT_ROTATION
+    private var endRotation = DEFAULT_ROTATION
+    private var navBarMode = DEFAULT_NAVBAR_MODE
+    private var extraConfig = mutableMapOf<String, Any?>()
+    private var description = ""
+
+    fun fromScenario(other: Scenario) =
+        forClass(other.testClass)
+            .withStartRotation(other.startRotation)
+            .withEndRotation(other.endRotation)
+            .withNavBarMode(other.navBarMode)
+            .withExtraConfigs(other.extraConfig)
+            .withDescriptionOverride(other.description)
+
+    fun forClass(cls: String) = apply { testClass = cls }
+
+    fun withStartRotation(rotation: Rotation) = apply { startRotation = rotation }
+
+    fun withEndRotation(rotation: Rotation) = apply { endRotation = rotation }
+
+    fun withNavBarMode(mode: NavBar) = apply { navBarMode = mode }
+
+    fun withExtraConfig(key: String, value: Any?) = apply { extraConfig[key] = value }
+
+    fun withExtraConfigs(cfg: Map<String, Any?>) = apply { extraConfig.putAll(cfg) }
+
+    fun withDescriptionOverride(description: String) = apply { this.description = description }
+
+    fun build(): Scenario {
+        require(testClass.isNotEmpty()) { "Test class missing" }
+        val description =
+            description.ifEmpty {
+                buildString {
+                    append(startRotation.description)
+                    if (endRotation != startRotation) {
+                        append("_${endRotation.description}")
+                    }
+                    append("_${navBarMode.description}")
+                }
+            }
+        return Scenario(testClass, startRotation, endRotation, navBarMode, extraConfig, description)
+    }
+
+    fun createEmptyScenario(): Scenario =
+        Scenario(
+            testClass = "",
+            startRotation = DEFAULT_ROTATION,
+            endRotation = DEFAULT_ROTATION,
+            navBarMode = DEFAULT_NAVBAR_MODE,
+            _extraConfig = emptyMap(),
+            description =
+                defaultDescription(DEFAULT_ROTATION, DEFAULT_ROTATION, DEFAULT_NAVBAR_MODE)
+        )
+
+    companion object {
+        const val FAAS_BLOCKING = "faas:blocking"
+        val DEFAULT_ROTATION = Rotation.ROTATION_0
+        val DEFAULT_NAVBAR_MODE = NavBar.MODE_GESTURAL
+
+        private fun defaultDescription(
+            startOrientation: Rotation,
+            endOrientation: Rotation,
+            navBarMode: NavBar
+        ): String = buildString {
+            append(startOrientation.description)
+            if (endOrientation != startOrientation) {
+                append("_${endOrientation.description}")
+            }
+            append("_${navBarMode.description}")
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/Tag.kt b/libraries/flicker/src/android/tools/common/Tag.kt
new file mode 100644
index 0000000..db6451b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Tag.kt
@@ -0,0 +1,29 @@
+/*
+ * 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
+
+/**
+ * Identify a trace location. By default, all traces have: [START], [END] and [ALL] locations,
+ * representing initial, final and all trace states.
+ *
+ * In addition, it is possible to create custom trace locations (tags).
+ */
+object Tag {
+    const val START = "start"
+    const val END = "end"
+    const val ALL = "all"
+}
diff --git a/libraries/flicker/src/android/tools/common/Timestamp.kt b/libraries/flicker/src/android/tools/common/Timestamp.kt
new file mode 100644
index 0000000..80949d4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/Timestamp.kt
@@ -0,0 +1,174 @@
+/*
+ * 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
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+import kotlin.math.max
+
+/**
+ * Time interface with all available timestamp types
+ *
+ * @param elapsedNanos Nanoseconds since boot, including time spent in sleep.
+ * @param systemUptimeNanos Nanoseconds since boot, not counting time spent in deep sleep
+ * @param unixNanos Nanoseconds since Unix epoch
+ */
+@JsExport
+data class Timestamp
+internal constructor(
+    val elapsedNanos: Long = 0L,
+    val systemUptimeNanos: Long = 0L,
+    val unixNanos: Long = 0L,
+    private val realTimestampFormatter: (Long) -> String
+) : Comparable<Timestamp> {
+    val hasElapsedTimestamp = elapsedNanos != 0L
+    val hasSystemUptimeTimestamp = systemUptimeNanos != 0L
+    val hasUnixTimestamp = unixNanos != 0L
+    val isEmpty = !hasElapsedTimestamp && !hasSystemUptimeTimestamp && !hasUnixTimestamp
+    val hasAllTimestamps = hasUnixTimestamp && hasSystemUptimeTimestamp && hasElapsedTimestamp
+
+    fun unixNanosToLogFormat(): String {
+        val seconds = unixNanos / SECOND_AS_NANOSECONDS
+        val nanos = unixNanos % SECOND_AS_NANOSECONDS
+        return "$seconds.${nanos.toString().padStart(9, '0')}"
+    }
+
+    override fun toString(): String {
+        return when {
+            isEmpty -> "<NO TIMESTAMP>"
+            hasUnixTimestamp -> "${realTimestampFormatter(unixNanos)} (${unixNanos}ns)"
+            hasSystemUptimeTimestamp ->
+                "${formatElapsedTimestamp(systemUptimeNanos)} (${systemUptimeNanos}ns)"
+            hasElapsedTimestamp -> "${formatElapsedTimestamp(elapsedNanos)} (${elapsedNanos}ns)"
+            else -> error("Timestamp had no valid timestamps sets")
+        }
+    }
+
+    @JsName("minusLong")
+    operator fun minus(nanos: Long): Timestamp {
+        val elapsedNanos = max(this.elapsedNanos - nanos, 0L)
+        val systemUptimeNanos = max(this.systemUptimeNanos - nanos, 0L)
+        val unixNanos = max(this.unixNanos - nanos, 0L)
+        return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
+    }
+
+    @JsName("minusTimestamp")
+    operator fun minus(timestamp: Timestamp): Timestamp {
+        val elapsedNanos =
+            if (this.hasElapsedTimestamp && timestamp.hasElapsedTimestamp) {
+                this.elapsedNanos - timestamp.elapsedNanos
+            } else {
+                0L
+            }
+        val systemUptimeNanos =
+            if (this.hasSystemUptimeTimestamp && timestamp.hasSystemUptimeTimestamp) {
+                this.systemUptimeNanos - timestamp.systemUptimeNanos
+            } else {
+                0L
+            }
+        val unixNanos =
+            if (this.hasUnixTimestamp && timestamp.hasUnixTimestamp) {
+                this.unixNanos - timestamp.unixNanos
+            } else {
+                0L
+            }
+        return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
+    }
+
+    enum class PreferredType {
+        ELAPSED,
+        SYSTEM_UPTIME,
+        UNIX,
+        ANY
+    }
+
+    // The preferred and most accurate time type to use when running Timestamp operations or
+    // comparisons
+    private val preferredType: PreferredType
+        get() =
+            when {
+                hasElapsedTimestamp && hasSystemUptimeTimestamp -> PreferredType.ANY
+                hasElapsedTimestamp -> PreferredType.ELAPSED
+                hasSystemUptimeTimestamp -> PreferredType.SYSTEM_UPTIME
+                hasUnixTimestamp -> PreferredType.UNIX
+                else -> error("No valid timestamp available")
+            }
+
+    override fun compareTo(other: Timestamp): Int {
+        var useType = PreferredType.ANY
+        if (other.preferredType == this.preferredType) {
+            useType = this.preferredType
+        } else if (this.preferredType == PreferredType.ANY) {
+            useType = other.preferredType
+        } else if (other.preferredType == PreferredType.ANY) {
+            useType = this.preferredType
+        }
+
+        return when (useType) {
+            PreferredType.ELAPSED -> this.elapsedNanos.compareTo(other.elapsedNanos)
+            PreferredType.SYSTEM_UPTIME -> this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
+            PreferredType.UNIX,
+            PreferredType.ANY -> {
+                when {
+                    // If preferred timestamps don't match then comparing UNIX timestamps is
+                    // probably most accurate
+                    this.hasUnixTimestamp && other.hasUnixTimestamp ->
+                        this.unixNanos.compareTo(other.unixNanos)
+                    // Assumes timestamps are collected from the same device
+                    this.hasElapsedTimestamp && other.hasElapsedTimestamp ->
+                        this.elapsedNanos.compareTo(other.elapsedNanos)
+                    this.hasSystemUptimeTimestamp && other.hasSystemUptimeTimestamp ->
+                        this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
+                    else -> error("Timestamps $this and $other are not comparable")
+                }
+            }
+        }
+    }
+
+    companion object {
+        fun formatElapsedTimestamp(timestampNs: Long): String {
+            var remainingNs = timestampNs
+            val prettyTimestamp = StringBuilder()
+
+            val timeUnitToNanoSeconds =
+                mapOf(
+                    "d" to DAY_AS_NANOSECONDS,
+                    "h" to HOUR_AS_NANOSECONDS,
+                    "m" to MINUTE_AS_NANOSECONDS,
+                    "s" to SECOND_AS_NANOSECONDS,
+                    "ms" to MILLISECOND_AS_NANOSECONDS,
+                    "ns" to 1,
+                )
+
+            for ((timeUnit, ns) in timeUnitToNanoSeconds) {
+                val convertedTime = remainingNs / ns
+                remainingNs %= ns
+                if (prettyTimestamp.isEmpty() && convertedTime == 0L) {
+                    // Trailing 0 unit
+                    continue
+                }
+                prettyTimestamp.append("$convertedTime$timeUnit")
+            }
+
+            if (prettyTimestamp.isEmpty()) {
+                return "0ns"
+            }
+
+            return prettyTimestamp.toString()
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/TimestampFactory.kt b/libraries/flicker/src/android/tools/common/TimestampFactory.kt
new file mode 100644
index 0000000..9c1a521
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/TimestampFactory.kt
@@ -0,0 +1,76 @@
+/*
+ * 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
+
+import kotlin.js.JsName
+
+class TimestampFactory(private val realTimestampFormatter: (Long) -> String = { it.toString() }) {
+    private val empty by lazy { Timestamp(0L, 0L, 0L, realTimestampFormatter) }
+    private val min by lazy { Timestamp(1, 1, 1, realTimestampFormatter) }
+    private val max by lazy {
+        Timestamp(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, realTimestampFormatter)
+    }
+
+    fun min(): Timestamp = min
+    fun max(): Timestamp = max
+    fun empty(): Timestamp = empty
+
+    @JsName("fromLong")
+    fun from(
+        elapsedNanos: Long? = null,
+        systemUptimeNanos: Long? = null,
+        unixNanos: Long? = null,
+    ): Timestamp {
+        return Timestamp(
+            elapsedNanos ?: 0L,
+            systemUptimeNanos ?: 0L,
+            unixNanos ?: 0L,
+            realTimestampFormatter
+        )
+    }
+
+    @JsName("fromString")
+    fun from(
+        elapsedNanos: String? = null,
+        systemUptimeNanos: String? = null,
+        unixNanos: String? = null,
+    ): Timestamp {
+        return from(
+            (elapsedNanos ?: "0").toLong(),
+            (systemUptimeNanos ?: "0").toLong(),
+            (unixNanos ?: "0").toLong()
+        )
+    }
+
+    @JsName("fromWithOffsetLong")
+    fun from(elapsedNanos: Long, elapsedOffsetNanos: Long): Timestamp {
+        return Timestamp(
+            elapsedNanos = elapsedNanos,
+            unixNanos = elapsedNanos + elapsedOffsetNanos,
+            realTimestampFormatter = realTimestampFormatter
+        )
+    }
+
+    @JsName("fromWithOffsetString")
+    fun from(elapsedNanos: String, elapsedOffsetNanos: String): Timestamp {
+        val elapsedNanosLong = elapsedNanos.toLong()
+        return from(
+            elapsedNanos = elapsedNanosLong,
+            unixNanos = elapsedNanosLong + elapsedOffsetNanos.toLong()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/ActiveBuffer.kt b/libraries/flicker/src/android/tools/common/datatypes/ActiveBuffer.kt
new file mode 100644
index 0000000..777f364
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/ActiveBuffer.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for ActiveBufferProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class ActiveBuffer private constructor(width: Int, height: Int, val stride: Int, val format: Int) :
+    Size(width, height) {
+    override fun prettyPrint(): String = "w:$width, h:$height, stride:$stride, format:$format"
+
+    override fun equals(other: Any?): Boolean =
+        other is ActiveBuffer &&
+            other.height == height &&
+            other.width == width &&
+            other.stride == stride &&
+            other.format == format
+
+    override fun hashCode(): Int {
+        var result = height
+        result = 31 * result + width
+        result = 31 * result + stride
+        result = 31 * result + format
+        return result
+    }
+
+    override fun toString(): String = prettyPrint()
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: ActiveBuffer
+            get() = withCache { ActiveBuffer(width = 0, height = 0, stride = 0, format = 0) }
+        @JsName("fromBuffer")
+        fun from(width: Int, height: Int, stride: Int, format: Int): ActiveBuffer = withCache {
+            ActiveBuffer(width, height, stride, format)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Color.kt b/libraries/flicker/src/android/tools/common/datatypes/Color.kt
new file mode 100644
index 0000000..1676ba7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Color.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for ColorProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class Color private constructor(r: Float, g: Float, b: Float, val a: Float) : Color3(r, g, b) {
+    override val isEmpty: Boolean
+        get() = a == 0f || r < 0 || g < 0 || b < 0
+
+    override val isNotEmpty: Boolean
+        get() = !isEmpty
+
+    @JsName("isOpaque") val isOpaque: Boolean = a == 1.0f
+
+    override fun prettyPrint(): String {
+        val parentPrint = super.prettyPrint()
+        return "$parentPrint a:$a"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Color) return false
+        if (!super.equals(other)) return false
+
+        if (a != other.a) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + a.hashCode()
+        return result
+    }
+
+    companion object {
+        val EMPTY: Color
+            get() = withCache { Color(r = -1f, g = -1f, b = -1f, a = 0f) }
+        val DEFAULT: Color
+            get() = withCache { Color(r = 0f, g = 0f, b = 0f, a = 1f) }
+
+        fun from(r: Float, g: Float, b: Float, a: Float): Color = withCache { Color(r, g, b, a) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Color3.kt b/libraries/flicker/src/android/tools/common/datatypes/Color3.kt
new file mode 100644
index 0000000..4f8d99c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Color3.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.FloatFormatter
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for Color3 (frameworks/native/services/surfaceflinger/layerproto/transactions.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+open class Color3(val r: Float, val g: Float, val b: Float) {
+    @JsName("isEmpty")
+    open val isEmpty: Boolean
+        get() = r < 0 || g < 0 || b < 0
+
+    @JsName("isNotEmpty")
+    open val isNotEmpty: Boolean
+        get() = !isEmpty
+
+    @JsName("prettyPrint")
+    open fun prettyPrint(): String {
+        val r = FloatFormatter.format(r)
+        val g = FloatFormatter.format(g)
+        val b = FloatFormatter.format(b)
+        return "r:$r g:$g b:$b"
+    }
+
+    override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Color3) return false
+
+        if (r != other.r) return false
+        if (g != other.g) return false
+        if (b != other.b) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = r.hashCode()
+        result = 31 * result + g.hashCode()
+        result = 31 * result + b.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Color3
+            get() = withCache { Color3(r = -1f, g = -1f, b = -1f) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Insets.kt b/libraries/flicker/src/android/tools/common/datatypes/Insets.kt
new file mode 100644
index 0000000..a020bf7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Insets.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for RectProto objects representing insets
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class Insets private constructor(left: Int = 0, top: Int = 0, right: Int = 0, bottom: Int = 0) :
+    Rect(left, top, right, bottom) {
+    override val isEmpty: Boolean
+        get() = left == 0 && top == 0 && right == 0 && bottom == 0
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Insets) return false
+        if (!super.equals(other)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + isEmpty.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Insets
+            get() = withCache { Insets() }
+
+        @JsName("from")
+        fun from(left: Int, top: Int, right: Int, bottom: Int): Insets = withCache {
+            Insets(left, top, right, bottom)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Matrix22.kt b/libraries/flicker/src/android/tools/common/datatypes/Matrix22.kt
new file mode 100644
index 0000000..d42c02c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Matrix22.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.FloatFormatter
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Representation of a matrix 3x3 used for layer transforms
+ * ```
+ *          |dsdx dsdy  tx|
+ * ```
+ * matrix = |dtdx dtdy ty|
+ * ```
+ *          |0    0     1 |
+ * ```
+ */
+@JsExport
+open class Matrix22(
+    @JsName("dsdx") val dsdx: Float,
+    @JsName("dtdx") val dtdx: Float,
+    @JsName("dsdy") val dsdy: Float,
+    @JsName("dtdy") val dtdy: Float
+) {
+    @JsName("prettyPrint")
+    open fun prettyPrint(): String {
+        val dsdx = FloatFormatter.format(dsdx)
+        val dtdx = FloatFormatter.format(dtdx)
+        val dsdy = FloatFormatter.format(dsdy)
+        val dtdy = FloatFormatter.format(dtdy)
+        return "dsdx:$dsdx   dtdx:$dtdx   dsdy:$dsdy   dtdy:$dtdy"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Matrix22) return false
+
+        if (dsdx != other.dsdx) return false
+        if (dtdx != other.dtdx) return false
+        if (dsdy != other.dsdy) return false
+        if (dtdy != other.dtdy) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = dsdx.hashCode()
+        result = 31 * result + dtdx.hashCode()
+        result = 31 * result + dsdy.hashCode()
+        result = 31 * result + dtdy.hashCode()
+        return result
+    }
+
+    override fun toString(): String = prettyPrint()
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Matrix22
+            get() = withCache { Matrix22(0f, 0f, 0f, 0f) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Matrix33.kt b/libraries/flicker/src/android/tools/common/datatypes/Matrix33.kt
new file mode 100644
index 0000000..6b1bc54
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Matrix33.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.FloatFormatter
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Representation of a matrix 3x3 used for layer transforms
+ * ```
+ *          |dsdx dsdy  tx|
+ * ```
+ * matrix = |dtdx dtdy ty|
+ * ```
+ *          |0    0     1 |
+ * ```
+ */
+@JsExport
+class Matrix33
+private constructor(
+    dsdx: Float = 0F,
+    dtdx: Float = 0F,
+    @JsName("tx") val tx: Float = 0F,
+    dsdy: Float = 0F,
+    dtdy: Float = 0F,
+    @JsName("ty") val ty: Float = 0F
+) : Matrix22(dsdx, dtdx, dsdy, dtdy) {
+    override fun prettyPrint(): String {
+        val parentPrint = super.prettyPrint()
+        val tx = FloatFormatter.format(dsdx)
+        val ty = FloatFormatter.format(dtdx)
+        return "$parentPrint   tx:$tx   ty:$ty"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Matrix33) return false
+        if (!super.equals(other)) return false
+
+        if (tx != other.tx) return false
+        if (ty != other.ty) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + tx.hashCode()
+        result = 31 * result + ty.hashCode()
+        return result
+    }
+
+    companion object {
+        val EMPTY: Matrix33
+            get() = withCache { from(dsdx = 0f, dtdx = 0f, tx = 0f, dsdy = 0f, dtdy = 0f, ty = 0f) }
+
+        @JsName("identity")
+        fun identity(x: Float, y: Float): Matrix33 = withCache {
+            from(dsdx = 1f, dtdx = 0f, x, dsdy = 0f, dtdy = 1f, y)
+        }
+
+        @JsName("rot270")
+        fun rot270(x: Float, y: Float): Matrix33 = withCache {
+            from(dsdx = 0f, dtdx = -1f, x, dsdy = 1f, dtdy = 0f, y)
+        }
+
+        @JsName("rot180")
+        fun rot180(x: Float, y: Float): Matrix33 = withCache {
+            from(dsdx = -1f, dtdx = 0f, x, dsdy = 0f, dtdy = -1f, y)
+        }
+
+        @JsName("rot90")
+        fun rot90(x: Float, y: Float): Matrix33 = withCache {
+            from(dsdx = 0f, dtdx = 1f, x, dsdy = -1f, dtdy = 0f, y)
+        }
+
+        @JsName("from")
+        fun from(
+            dsdx: Float,
+            dtdx: Float,
+            tx: Float,
+            dsdy: Float,
+            dtdy: Float,
+            ty: Float
+        ): Matrix33 = withCache { Matrix33(dsdx, dtdx, tx, dsdy, dtdy, ty) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Point.kt b/libraries/flicker/src/android/tools/common/datatypes/Point.kt
new file mode 100644
index 0000000..01b34a2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Point.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for PointProto (frameworks/base/core/proto/android/graphics/point.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class Point private constructor(val x: Int, val y: Int) {
+    @JsName("prettyPrint") fun prettyPrint(): String = "($x, $y)"
+
+    override fun toString(): String = prettyPrint()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Point) return false
+
+        if (x != other.x) return false
+        if (y != other.y) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = x
+        result = 31 * result + y
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Point
+            get() = withCache { Point(x = 0, y = 0) }
+
+        @JsName("from") fun from(x: Int, y: Int): Point = withCache { Point(x, y) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/PointF.kt b/libraries/flicker/src/android/tools/common/datatypes/PointF.kt
new file mode 100644
index 0000000..6b76253
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/PointF.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.FloatFormatter
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for PositionProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class PointF private constructor(val x: Float, val y: Float) {
+    @JsName("prettyPrint")
+    fun prettyPrint(): String = "(${FloatFormatter.format(x)}, ${FloatFormatter.format(y)})"
+
+    override fun toString(): String = prettyPrint()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is PointF) return false
+
+        if (x != other.x) return false
+        if (y != other.y) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: PointF
+            get() = withCache { PointF(x = 0f, y = 0f) }
+
+        @JsName("from") fun from(x: Float, y: Float): PointF = withCache { PointF(x, y) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Rect.kt b/libraries/flicker/src/android/tools/common/datatypes/Rect.kt
new file mode 100644
index 0000000..b3ba9f1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Rect.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for RectProto
+ * ```
+ *     - frameworks/native/services/surfaceflinger/layerproto/common.proto and
+ *     - frameworks/base/core/proto/android/graphics/rect.proto
+ * ```
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+open class Rect
+protected constructor(
+    @JsName("left") val left: Int = 0,
+    @JsName("top") val top: Int = 0,
+    @JsName("right") val right: Int = 0,
+    @JsName("bottom") val bottom: Int = 0
+) {
+    @JsName("height")
+    val height: Int
+        get() = bottom - top
+    @JsName("width")
+    val width: Int
+        get() = right - left
+    @JsName("centerX") fun centerX(): Int = (left + right) / 2
+    @JsName("centerY") fun centerY(): Int = (top + bottom) / 2
+    /** Returns true if the rectangle is empty (left >= right or top >= bottom) */
+    @JsName("isEmpty")
+    open val isEmpty: Boolean
+        get() = width <= 0 || height <= 0
+
+    @JsName("isNotEmpty")
+    val isNotEmpty: Boolean
+        get() = !isEmpty
+
+    /** Returns a [RectF] version fo this rectangle. */
+    @JsName("toRectF")
+    fun toRectF(): RectF {
+        return RectF.from(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
+    }
+
+    @JsName("prettyPrint")
+    fun prettyPrint(): String = if (isEmpty) "[empty]" else "($left, $top) - ($right, $bottom)"
+
+    /**
+     * Returns true iff the specified rectangle r is inside or equal to this rectangle. An empty
+     * rectangle never contains another rectangle.
+     *
+     * @param rect The rectangle being tested for containment.
+     * @return true iff the specified rectangle r is inside or equal to this
+     * ```
+     *              rectangle
+     * ```
+     */
+    operator fun contains(rect: Rect): Boolean {
+        val thisRect = toRectF()
+        val otherRect = rect.toRectF()
+        return thisRect.contains(otherRect)
+    }
+
+    /**
+     * Returns a [Rect] where the dimensions don't exceed those of [crop]
+     *
+     * @param crop The crop that should be applied to this layer
+     */
+    @JsName("crop")
+    fun crop(crop: Rect): Rect {
+        val newLeft = maxOf(left, crop.left)
+        val newTop = maxOf(top, crop.top)
+        val newRight = minOf(right, crop.right)
+        val newBottom = minOf(bottom, crop.bottom)
+        return from(newLeft, newTop, newRight, newBottom)
+    }
+
+    /**
+     * Returns true if: fLeft <= x < fRight && fTop <= y < fBottom. Returns false if SkIRect is
+     * empty.
+     *
+     * Considers input to describe constructed SkIRect: (x, y, x + 1, y + 1) and returns true if
+     * constructed area is completely enclosed by SkIRect area.
+     *
+     * @param x test SkIPoint x-coordinate @param y test SkIPoint y-coordinate @return true if (x,
+     * y) is inside SkIRect
+     */
+    @JsName("containsPoint")
+    fun contains(x: Int, y: Int): Boolean {
+        return x in left until right && y in top until bottom
+    }
+
+    /**
+     * If the specified rectangle intersects this rectangle, return true and set this rectangle to
+     * that intersection, otherwise return false and do not change this rectangle. No check is
+     * performed to see if either rectangle is empty. To just test for intersection, use
+     * intersects()
+     *
+     * @param rect The rectangle being intersected with this rectangle.
+     * @return A rectangle with the intersection coordinates
+     */
+    @JsName("intersection")
+    fun intersection(rect: Rect): Rect {
+        val thisRect = toRectF()
+        val otherRect = rect.toRectF()
+        return thisRect.intersection(otherRect).toRect()
+    }
+
+    override fun hashCode(): Int {
+        var result = left
+        result = 31 * result + top
+        result = 31 * result + right
+        result = 31 * result + bottom
+        return result
+    }
+
+    override fun toString(): String = prettyPrint()
+
+    @JsName("clone")
+    fun clone(): Rect {
+        return from(left, top, right, bottom)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Rect) return false
+
+        if (left != other.left) return false
+        if (top != other.top) return false
+        if (right != other.right) return false
+        if (bottom != other.bottom) return false
+
+        return true
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Rect
+            get() = withCache { Rect() }
+
+        @JsName("from")
+        fun from(left: Int, top: Int, right: Int, bottom: Int): Rect = withCache {
+            Rect(left, top, right, bottom)
+        }
+
+        internal fun withoutCache(left: Int, top: Int, right: Int, bottom: Int): Rect =
+            Rect(left, top, right, bottom)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/RectF.kt b/libraries/flicker/src/android/tools/common/datatypes/RectF.kt
new file mode 100644
index 0000000..af35cbe
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/RectF.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.FloatFormatter
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for FloatRectProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class RectF
+private constructor(
+    @JsName("left") val left: Float,
+    @JsName("top") val top: Float,
+    @JsName("right") val right: Float,
+    @JsName("bottom") val bottom: Float
+) {
+    @JsName("height")
+    val height: Float
+        get() = bottom - top
+    @JsName("width")
+    val width: Float
+        get() = right - left
+
+    /** Returns true if the rectangle is empty (left >= right or top >= bottom) */
+    @JsName("isEmpty")
+    val isEmpty: Boolean
+        get() = width <= 0f || height <= 0f
+    @JsName("isNotEmpty")
+    val isNotEmpty: Boolean
+        get() = !isEmpty
+
+    /**
+     * Returns a [Rect] version fo this rectangle.
+     *
+     * All fractional parts are rounded to 0
+     */
+    @JsName("toRect")
+    fun toRect(): Rect {
+        return Rect.from(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
+    }
+
+    /**
+     * Returns true iff the specified rectangle r is inside or equal to this rectangle. An empty
+     * rectangle never contains another rectangle.
+     *
+     * @param r The rectangle being tested for containment.
+     * @return true iff the specified rectangle r is inside or equal to this
+     * ```
+     *              rectangle
+     * ```
+     */
+    operator fun contains(r: RectF): Boolean {
+        // check for empty first
+        return this.left < this.right &&
+            this.top < this.bottom && // now check for containment
+            left <= r.left &&
+            top <= r.top &&
+            right >= r.right &&
+            bottom >= r.bottom
+    }
+
+    /**
+     * Returns a [RectF] where the dimensions don't exceed those of [crop]
+     *
+     * @param crop The crop that should be applied to this layer
+     */
+    @JsName("crop")
+    fun crop(crop: RectF): RectF {
+        val newLeft = maxOf(left, crop.left)
+        val newTop = maxOf(top, crop.top)
+        val newRight = minOf(right, crop.right)
+        val newBottom = minOf(bottom, crop.bottom)
+        return from(newLeft, newTop, newRight, newBottom)
+    }
+
+    /**
+     * If the rectangle specified by left,top,right,bottom intersects this rectangle, return true
+     * and set this rectangle to that intersection, otherwise return false and do not change this
+     * rectangle. No check is performed to see if either rectangle is empty. Note: To just test for
+     * intersection, use intersects()
+     *
+     * @param left The left side of the rectangle being intersected with this rectangle
+     * @param top The top of the rectangle being intersected with this rectangle
+     * @param right The right side of the rectangle being intersected with this rectangle.
+     * @param bottom The bottom of the rectangle being intersected with this rectangle.
+     * @return A rectangle with the intersection coordinates
+     */
+    @JsName("intersection")
+    fun intersection(left: Float, top: Float, right: Float, bottom: Float): RectF {
+        if (this.left < right && left < this.right && this.top <= bottom && top <= this.bottom) {
+            var intersectionLeft = this.left
+            var intersectionTop = this.top
+            var intersectionRight = this.right
+            var intersectionBottom = this.bottom
+
+            if (this.left < left) {
+                intersectionLeft = left
+            }
+            if (this.top < top) {
+                intersectionTop = top
+            }
+            if (this.right > right) {
+                intersectionRight = right
+            }
+            if (this.bottom > bottom) {
+                intersectionBottom = bottom
+            }
+            return from(intersectionLeft, intersectionTop, intersectionRight, intersectionBottom)
+        }
+        return EMPTY
+    }
+
+    /**
+     * If the specified rectangle intersects this rectangle, return true and set this rectangle to
+     * that intersection, otherwise return false and do not change this rectangle. No check is
+     * performed to see if either rectangle is empty. To just test for intersection, use
+     * intersects()
+     *
+     * @param r The rectangle being intersected with this rectangle.
+     * @return A rectangle with the intersection coordinates
+     */
+    @JsName("intersectionWithRect")
+    fun intersection(r: RectF): RectF = intersection(r.left, r.top, r.right, r.bottom)
+
+    @JsName("prettyPrint")
+    fun prettyPrint(): String =
+        if (isEmpty) {
+            "[empty]"
+        } else {
+            val left = FloatFormatter.format(left)
+            val top = FloatFormatter.format(top)
+            val right = FloatFormatter.format(right)
+            val bottom = FloatFormatter.format(bottom)
+            "($left, $top) - ($right, $bottom)"
+        }
+
+    override fun toString(): String = prettyPrint()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RectF) return false
+
+        if (left != other.left) return false
+        if (top != other.top) return false
+        if (right != other.right) return false
+        if (bottom != other.bottom) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = left.hashCode()
+        result = 31 * result + top.hashCode()
+        result = 31 * result + right.hashCode()
+        result = 31 * result + bottom.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: RectF
+            get() = withCache { RectF(left = 0f, top = 0f, right = 0f, bottom = 0f) }
+
+        @JsName("from")
+        fun from(left: Float, top: Float, right: Float, bottom: Float): RectF = withCache {
+            RectF(left, top, right, bottom)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Region.kt b/libraries/flicker/src/android/tools/common/datatypes/Region.kt
new file mode 100644
index 0000000..06762f6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Region.kt
@@ -0,0 +1,1216 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.datatypes.Region.Companion.from
+import kotlin.js.JsExport
+import kotlin.js.JsName
+import kotlin.math.min
+
+/**
+ * Wrapper for RegionProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
+ *
+ * Implementation based android.graphics.Region's native implementation found in SkRegion.cpp
+ *
+ * This class is used by flicker and Winscope
+ *
+ * It has a single constructor and different [from] functions on its companion because JS doesn't
+ * support constructor overload
+ */
+@JsExport
+class Region(rects: Array<Rect> = arrayOf()) {
+    private var fBounds = Rect.EMPTY
+    private var fRunHead: RunHead? = RunHead(isEmptyHead = true)
+
+    init {
+        if (rects.isEmpty()) {
+            setEmpty()
+        } else {
+            for (rect in rects) {
+                union(rect)
+            }
+        }
+    }
+
+    @JsName("rects")
+    val rects
+        get() = getRectsFromString(toString())
+
+    @JsName("width")
+    val width: Int
+        get() = bounds.width
+    @JsName("height")
+    val height: Int
+        get() = bounds.height
+
+    // if null we are a rect not empty
+    @JsName("isEmpty")
+    val isEmpty: Boolean
+        get() = fRunHead?.isEmptyHead ?: false
+    @JsName("isNotEmpty")
+    val isNotEmpty: Boolean
+        get() = !isEmpty
+    @JsName("bounds")
+    val bounds
+        get() = fBounds
+
+    /** Set the region to the empty region */
+    @JsName("setEmpty")
+    fun setEmpty(): Boolean {
+        fBounds = Rect.EMPTY
+        fRunHead = RunHead(isEmptyHead = true)
+
+        return false
+    }
+
+    /** Set the region to the specified region. */
+    @JsName("setRegion")
+    fun set(region: Region): Boolean {
+        fBounds = region.fBounds.clone()
+        fRunHead = region.fRunHead?.clone()
+        return !(fRunHead?.isEmptyHead ?: false)
+    }
+
+    /** Set the region to the specified rectangle */
+    @JsName("setRect")
+    fun set(r: Rect): Boolean {
+        return if (
+            r.isEmpty ||
+                SkRegion_kRunTypeSentinel == r.right ||
+                SkRegion_kRunTypeSentinel == r.bottom
+        ) {
+            this.setEmpty()
+        } else {
+            fBounds = r
+            fRunHead = null
+            true
+        }
+    }
+
+    /** Set the region to the specified rectangle */
+    @JsName("set")
+    operator fun set(left: Int, top: Int, right: Int, bottom: Int): Boolean {
+        return set(Rect.withoutCache(left, top, right, bottom))
+    }
+
+    @JsName("isRect")
+    fun isRect(): Boolean {
+        return fRunHead == null
+    }
+
+    @JsName("isComplex")
+    fun isComplex(): Boolean {
+        return !this.isEmpty && !this.isRect()
+    }
+
+    @JsName("contains")
+    fun contains(x: Int, y: Int): Boolean {
+        if (!fBounds.contains(x, y)) {
+            return false
+        }
+        if (this.isRect()) {
+            return true
+        }
+        require(this.isComplex())
+
+        val runs = fRunHead!!.findScanline(y)
+
+        // Skip the Bottom and IntervalCount
+        var runsIndex = 2
+
+        // Just walk this scanline, checking each interval. The X-sentinel will
+        // appear as a left-interval (runs[0]) and should abort the search.
+        //
+        // We could do a bsearch, using interval-count (runs[1]), but need to time
+        // when that would be worthwhile.
+        //
+        while (true) {
+            if (x < runs[runsIndex]) {
+                break
+            }
+            if (x < runs[runsIndex + 1]) {
+                return true
+            }
+            runsIndex += 2
+        }
+        return false
+    }
+
+    override fun toString(): String = prettyPrint()
+
+    class Iterator(private val rgn: Region) {
+        private var done: Boolean
+        private var rect: Rect
+        private var fRuns: Array<Int>? = null
+        private var fRunsIndex = 0
+
+        init {
+            fRunsIndex = 0
+            if (rgn.isEmpty) {
+                rect = Rect.EMPTY
+                done = true
+            } else {
+                done = false
+                if (rgn.isRect()) {
+                    rect = rgn.fBounds
+                    fRuns = null
+                } else {
+                    fRuns = rgn.fRunHead!!.readonlyRuns
+                    rect = Rect.withoutCache(fRuns!![3], fRuns!![0], fRuns!![4], fRuns!![1])
+                    fRunsIndex = 5
+                }
+            }
+        }
+
+        fun next() {
+            if (done) {
+                return
+            }
+
+            if (fRuns == null) { // rect case
+                done = true
+                return
+            }
+
+            val runs = fRuns!!
+            var runsIndex = fRunsIndex
+
+            if (runs[runsIndex] < SkRegion_kRunTypeSentinel) { // valid X value
+                rect =
+                    Rect.withoutCache(runs[runsIndex], rect.top, runs[runsIndex + 1], rect.bottom)
+                runsIndex += 2
+            } else { // we're at the end of a line
+                runsIndex += 1
+                if (runs[runsIndex] < SkRegion_kRunTypeSentinel) { // valid Y value
+                    val intervals = runs[runsIndex + 1]
+                    if (0 == intervals) { // empty line
+                        rect =
+                            Rect.withoutCache(rect.left, runs[runsIndex], rect.right, rect.bottom)
+                        runsIndex += 3
+                    } else {
+                        rect = Rect.withoutCache(rect.left, rect.bottom, rect.right, rect.bottom)
+                    }
+
+                    assertSentinel(runs[runsIndex + 2], false)
+                    assertSentinel(runs[runsIndex + 3], false)
+                    rect =
+                        Rect.withoutCache(
+                            runs[runsIndex + 2],
+                            rect.top,
+                            runs[runsIndex + 3],
+                            runs[runsIndex]
+                        )
+                    runsIndex += 4
+                } else { // end of rgn
+                    done = true
+                }
+            }
+            fRunsIndex = runsIndex
+        }
+
+        @JsName("done")
+        fun done(): Boolean {
+            return done
+        }
+
+        fun rect(): Rect {
+            return rect
+        }
+    }
+
+    @JsName("prettyPrint")
+    fun prettyPrint(): String {
+        val iter = Iterator(this)
+        val result = StringBuilder("SkRegion(")
+        while (!iter.done()) {
+            val r = iter.rect()
+            result.append("(${r.left},${r.top},${r.right},${r.bottom})")
+            iter.next()
+        }
+        result.append(")")
+        return result.toString()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Region) return false
+        if (!rects.contentEquals(other.rects)) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = 1
+        val iter = Iterator(this)
+        while (!iter.done()) {
+            result = 31 * result + iter.rect().hashCode()
+            iter.next()
+        }
+        return result
+    }
+
+    // the native values for these must match up with the enum in SkRegion.h
+    enum class Op(val nativeInt: Int) {
+        DIFFERENCE(0),
+        INTERSECT(1),
+        UNION(2),
+        XOR(3),
+        REVERSE_DIFFERENCE(4),
+        REPLACE(5)
+    }
+
+    @JsName("union")
+    fun union(r: Rect): Boolean {
+        return op(r, Op.UNION)
+    }
+
+    @JsName("toRectF")
+    fun toRectF(): RectF {
+        return bounds.toRectF()
+    }
+
+    private fun oper(rgnA: Region, rgnB: Region, op: Op): Boolean {
+        // simple cases
+        when (op) {
+            Op.REPLACE -> {
+                this.set(rgnB)
+                return !rgnB.isEmpty
+            }
+            Op.REVERSE_DIFFERENCE -> {
+                // collapse difference and reverse-difference into just difference
+                return this.oper(rgnB, rgnA, Op.DIFFERENCE)
+            }
+            Op.DIFFERENCE -> {
+                if (rgnA.isEmpty) {
+                    this.setEmpty()
+                    return false
+                }
+                if (rgnB.isEmpty || rgnA.bounds.intersection(rgnB.bounds).isEmpty) {
+                    this.set(rgnA)
+                    return !rgnA.isEmpty
+                }
+                if (rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds)) {
+                    this.setEmpty()
+                    return false
+                }
+            }
+            Op.INTERSECT -> {
+                when {
+                    rgnA.isEmpty ||
+                        rgnB.isEmpty ||
+                        rgnA.bounds.intersection(rgnB.bounds).isEmpty -> {
+                        this.setEmpty()
+                        return false
+                    }
+                    rgnA.isRect() && rgnB.isRect() -> {
+                        val rectIntersection = rgnA.bounds.intersection(rgnB.bounds)
+                        this.set(rgnA.bounds.intersection(rgnB.bounds))
+                        return !rectIntersection.isEmpty
+                    }
+                    rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
+                        this.set(rgnB)
+                        return !rgnB.isEmpty
+                    }
+                    rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
+                        this.set(rgnA)
+                        return !rgnA.isEmpty
+                    }
+                }
+            }
+            Op.UNION -> {
+                when {
+                    rgnA.isEmpty -> {
+                        this.set(rgnB)
+                        return !rgnB.isEmpty
+                    }
+                    rgnB.isEmpty -> {
+                        this.set(rgnA)
+                        return !rgnA.isEmpty
+                    }
+                    rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
+                        this.set(rgnA)
+                        return !rgnA.isEmpty
+                    }
+                    rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
+                        this.set(rgnB)
+                        return !rgnB.isEmpty
+                    }
+                }
+            }
+            Op.XOR -> {
+                when {
+                    rgnA.isEmpty -> {
+                        this.set(rgnB)
+                        return !rgnB.isEmpty
+                    }
+                    rgnB.isEmpty -> {
+                        this.set(rgnA)
+                        return !rgnA.isEmpty
+                    }
+                }
+            }
+        }
+
+        val array = RunArray()
+        val count = operate(rgnA.getRuns(), rgnB.getRuns(), array, op)
+        require(count <= array.count)
+        return this.setRuns(array, count)
+    }
+
+    class RunArray {
+        private val kRunArrayStackCount = 256
+        var runs: Array<Int> = Array(kRunArrayStackCount) { 0 }
+        private var fCount: Int = kRunArrayStackCount
+
+        val count: Int
+            get() = fCount
+
+        operator fun get(i: Int): Int {
+            return runs[i]
+        }
+
+        fun resizeToAtLeast(_count: Int) {
+            var count = _count
+            if (count > fCount) {
+                // leave at least 50% extra space for future growth.
+                count += count shr 1
+                val newRuns = Array(count) { 0 }
+                runs.forEachIndexed { index, value -> newRuns[index] = value }
+                runs = newRuns
+                fCount = count
+            }
+        }
+
+        operator fun set(i: Int, value: Int) {
+            runs[i] = value
+        }
+
+        fun subList(startIndex: Int, stopIndex: Int): RunArray {
+            val subRuns = RunArray()
+            subRuns.resizeToAtLeast(this.fCount)
+            for (i in startIndex until stopIndex) {
+                subRuns.runs[i - startIndex] = this.runs[i]
+            }
+            return subRuns
+        }
+
+        fun clone(): RunArray {
+            val clone = RunArray()
+            clone.runs = runs.copyOf()
+            clone.fCount = fCount
+            return clone
+        }
+    }
+
+    /**
+     * Set this region to the result of performing the Op on the specified regions. Return true if
+     * the result is not empty.
+     */
+    @JsName("opRegions")
+    fun op(rgnA: Region, rgnB: Region, op: Op): Boolean {
+        return this.oper(rgnA, rgnB, op)
+    }
+
+    private fun getRuns(): Array<Int> {
+        val runs: Array<Int>
+        if (this.isEmpty) {
+            runs = Array(kRectRegionRuns) { 0 }
+            runs[0] = SkRegion_kRunTypeSentinel
+        } else if (this.isRect()) {
+            runs = buildRectRuns(fBounds)
+        } else {
+            runs = fRunHead!!.readonlyRuns
+        }
+
+        return runs
+    }
+
+    private fun buildRectRuns(bounds: Rect): Array<Int> {
+        val runs = Array(kRectRegionRuns) { 0 }
+        runs[0] = bounds.top
+        runs[1] = bounds.bottom
+        runs[2] = 1 // 1 interval for this scanline
+        runs[3] = bounds.left
+        runs[4] = bounds.right
+        runs[5] = SkRegion_kRunTypeSentinel
+        runs[6] = SkRegion_kRunTypeSentinel
+        return runs
+    }
+
+    class RunHead(val isEmptyHead: Boolean = false) {
+        fun setRuns(runs: RunArray, count: Int) {
+            this.runs = runs
+            this.fRunCount = count
+        }
+
+        fun computeRunBounds(): Rect {
+            var runsIndex = 0
+            val top = runs[runsIndex]
+            runsIndex++
+
+            var bot: Int
+            var ySpanCount = 0
+            var intervalCount = 0
+            var left = Int.MAX_VALUE
+            var right = Int.MIN_VALUE
+
+            do {
+                bot = runs[runsIndex]
+                runsIndex++
+                require(bot < SkRegion_kRunTypeSentinel)
+                ySpanCount += 1
+
+                val intervals = runs[runsIndex]
+                runsIndex++
+                require(intervals >= 0)
+                require(intervals < SkRegion_kRunTypeSentinel)
+
+                if (intervals > 0) {
+                    val L = runs[runsIndex]
+                    require(L < SkRegion_kRunTypeSentinel)
+                    if (left > L) {
+                        left = L
+                    }
+
+                    runsIndex += intervals * 2
+                    val R = runs[runsIndex - 1]
+                    require(R < SkRegion_kRunTypeSentinel)
+                    if (right < R) {
+                        right = R
+                    }
+
+                    intervalCount += intervals
+                }
+                require(SkRegion_kRunTypeSentinel == runs[runsIndex])
+                runsIndex += 1 // skip x-sentinel
+
+                // test Y-sentinel
+            } while (SkRegion_kRunTypeSentinel > runs[runsIndex])
+
+            fYSpanCount = ySpanCount
+            fIntervalCount = intervalCount
+
+            return Rect.from(left, top, right, bot)
+        }
+
+        fun clone(): RunHead {
+            val clone = RunHead(isEmptyHead)
+            clone.fIntervalCount = fIntervalCount
+            clone.fYSpanCount = fYSpanCount
+            clone.runs = runs.clone()
+            clone.fRunCount = fRunCount
+            return clone
+        }
+
+        /**
+         * Return the scanline that contains the Y value. This requires that the Y value is already
+         * known to be contained within the bounds of the region, and so this routine never returns
+         * nullptr.
+         *
+         * It returns the beginning of the scanline, starting with its Bottom value.
+         */
+        fun findScanline(y: Int): Array<Int> {
+            val runs = readonlyRuns
+
+            // if the top-check fails, we didn't do a quick check on the bounds
+            require(y >= runs[0])
+
+            var runsIndex = 1 // skip top-Y
+            while (true) {
+                val bottom = runs[runsIndex]
+                // If we hit this, we've walked off the region, and our bounds check
+                // failed.
+                require(bottom < SkRegion_kRunTypeSentinel)
+                if (y < bottom) {
+                    break
+                }
+                runsIndex = skipEntireScanline(runsIndex)
+            }
+
+            val newArray = Array(runs.size - runsIndex) { 0 }
+            runs.copyInto(
+                newArray,
+                destinationOffset = 0,
+                startIndex = runsIndex,
+                endIndex = runs.size - runsIndex
+            )
+            return newArray
+        }
+
+        /**
+         * Given a scanline (including its Bottom value at runs[0]), return the next scanline.
+         * Asserts that there is one (i.e. runs[0] < Sentinel)
+         */
+        fun skipEntireScanline(_runsIndex: Int): Int {
+            var runsIndex = _runsIndex
+            // we are not the Y Sentinel
+            require(runs[runsIndex] < SkRegion_kRunTypeSentinel)
+
+            val intervals = runs[runsIndex + 1]
+            require(runs[runsIndex + 2 + intervals * 2] == SkRegion_kRunTypeSentinel)
+
+            // skip the entire line [B N [L R] S]
+            runsIndex += 1 + 1 + intervals * 2 + 1
+            return runsIndex
+        }
+
+        private var fIntervalCount: Int = 0
+        private var fYSpanCount: Int = 0
+        var runs = RunArray()
+        var fRunCount: Int = 0
+
+        val readonlyRuns: Array<Int>
+            get() = runs.runs
+    }
+
+    private fun setRuns(runs: RunArray, _count: Int): Boolean {
+        require(_count > 0)
+
+        var count = _count
+
+        if (isRunCountEmpty(count)) {
+            assertSentinel(runs[count - 1], true)
+            return this.setEmpty()
+        }
+
+        // trim off any empty spans from the top and bottom
+        // weird I should need this, perhaps op() could be smarter...
+        var trimmedRuns = runs
+        if (count > kRectRegionRuns) {
+            var stopIndex = count
+            assertSentinel(runs[0], false) // top
+            assertSentinel(runs[1], false) // bottom
+            // runs[2] is uncomputed intervalCount
+
+            var trimLeft = false
+            if (runs[3] == SkRegion_kRunTypeSentinel) { // should be first left...
+                trimLeft = true
+                assertSentinel(runs[1], false) // bot: a sentinel would mean two in a row
+                assertSentinel(runs[2], false) // interval count
+                assertSentinel(runs[3], false) // left
+                assertSentinel(runs[4], false) // right
+            }
+
+            assertSentinel(runs[stopIndex - 1], true)
+            assertSentinel(runs[stopIndex - 2], true)
+
+            var trimRight = false
+            // now check for a trailing empty span
+            if (runs[stopIndex - 5] == SkRegion_kRunTypeSentinel) {
+                // eek, stop[-4] was a bottom with no x-runs
+                trimRight = true
+            }
+
+            var startIndex = 0
+            if (trimLeft) {
+                startIndex += 3
+                trimmedRuns = trimmedRuns.subList(startIndex, count) // skip empty initial span
+                trimmedRuns[0] = runs[1] // set new top to prev bottom
+            }
+            if (trimRight) {
+                // kill empty last span
+                trimmedRuns[stopIndex - 4] = SkRegion_kRunTypeSentinel
+                stopIndex -= 3
+                assertSentinel(runs[stopIndex - 1], true) // last y-sentinel
+                assertSentinel(runs[stopIndex - 2], true) // last x-sentinel
+                assertSentinel(runs[stopIndex - 3], false) // last right
+                assertSentinel(runs[stopIndex - 4], false) // last left
+                assertSentinel(runs[stopIndex - 5], false) // last interval-count
+                assertSentinel(runs[stopIndex - 6], false) // last bottom
+                trimmedRuns = trimmedRuns.subList(startIndex, stopIndex)
+            }
+
+            count = stopIndex - startIndex
+        }
+
+        require(count >= kRectRegionRuns)
+
+        if (runsAreARect(trimmedRuns, count)) {
+            fBounds =
+                Rect.withoutCache(trimmedRuns[3], trimmedRuns[0], trimmedRuns[4], trimmedRuns[1])
+            return this.setRect(fBounds)
+        }
+
+        //  if we get here, we need to become a complex region
+        if (!this.isComplex() || fRunHead!!.fRunCount != count) {
+            fRunHead = RunHead()
+            fRunHead!!.fRunCount = count
+            require(this.isComplex())
+        }
+
+        // must call this before we can write directly into runs()
+        // in case we are sharing the buffer with another region (copy on write)
+        // fRunHead = fRunHead->ensureWritable();
+        // memcpy(fRunHead, runs, count * sizeof(RunType))
+        fRunHead!!.setRuns(trimmedRuns, count)
+        fBounds = fRunHead!!.computeRunBounds()
+
+        // Our computed bounds might be too large, so we have to check here.
+        if (fBounds.isEmpty) {
+            return this.setEmpty()
+        }
+
+        return true
+    }
+
+    private fun setRect(r: Rect): Boolean {
+        if (
+            r.isEmpty ||
+                SkRegion_kRunTypeSentinel == r.right ||
+                SkRegion_kRunTypeSentinel == r.bottom
+        ) {
+            return this.setEmpty()
+        }
+        fBounds = r
+        fRunHead = null
+        return true
+    }
+
+    private fun isRunCountEmpty(count: Int): Boolean {
+        return count <= 2
+    }
+
+    private fun runsAreARect(runs: RunArray, count: Int): Boolean {
+        require(count >= kRectRegionRuns)
+
+        if (count == kRectRegionRuns) {
+            assertSentinel(runs[1], false) // bottom
+            require(1 == runs[2])
+            assertSentinel(runs[3], false) // left
+            assertSentinel(runs[4], false) // right
+            assertSentinel(runs[5], true)
+            assertSentinel(runs[6], true)
+
+            require(runs[0] < runs[1]) // valid height
+            require(runs[3] < runs[4]) // valid width
+
+            return true
+        }
+        return false
+    }
+
+    class RgnOper(var top: Int, private val runArray: RunArray, op: Op) {
+        private val fMin = gOpMinMax[op]!!.min
+        private val fMax = gOpMinMax[op]!!.max
+
+        private var fStartDst = 0
+        private var fPrevDst = 1
+        private var fPrevLen = 0
+
+        fun addSpan(
+            bottom: Int,
+            aRuns: Array<Int>,
+            bRuns: Array<Int>,
+            aRunsIndex: Int,
+            bRunsIndex: Int
+        ) {
+            // skip X values and slots for the next Y+intervalCount
+            val start = fPrevDst + fPrevLen + 2
+            // start points to beginning of dst interval
+            val stop =
+                operateOnSpan(aRuns, bRuns, aRunsIndex, bRunsIndex, runArray, start, fMin, fMax)
+            val len = stop - start
+            require(len >= 1 && (len and 1) == 1)
+            require(SkRegion_kRunTypeSentinel == runArray[stop - 1])
+
+            // Assert memcmp won't exceed fArray->count().
+            require(runArray.count >= start + len - 1)
+            if (
+                fPrevLen == len &&
+                    (1 == len ||
+                        runArray
+                            .subList(fPrevDst, fPrevDst + len)
+                            .runs
+                            .contentEquals(runArray.subList(start, start + len).runs))
+            ) {
+                // update Y value
+                runArray[fPrevDst - 2] = bottom
+            } else { // accept the new span
+                if (len == 1 && fPrevLen == 0) {
+                    top = bottom // just update our bottom
+                } else {
+                    runArray[start - 2] = bottom
+                    runArray[start - 1] = len / 2 // len shr 1
+                    fPrevDst = start
+                    fPrevLen = len
+                }
+            }
+        }
+
+        fun flush(): Int {
+            runArray[fStartDst] = top
+            // Previously reserved enough for TWO sentinals.
+            // SkASSERT(fArray->count() > SkToInt(fPrevDst + fPrevLen));
+            runArray[fPrevDst + fPrevLen] = SkRegion_kRunTypeSentinel
+            return fPrevDst - fStartDst + fPrevLen + 1
+        }
+
+        class SpanRect(
+            private val aRuns: Array<Int>,
+            private val bRuns: Array<Int>,
+            aIndex: Int,
+            bIndex: Int
+        ) {
+            var fLeft: Int = 0
+            var fRight: Int = 0
+            var fInside: Int = 0
+
+            var fALeft: Int
+            var fARight: Int
+            var fBLeft: Int
+            var fBRight: Int
+            var fARuns: Int
+            var fBRuns: Int
+
+            init {
+                fALeft = aRuns[aIndex]
+                fARight = aRuns[aIndex + 1]
+                fBLeft = bRuns[bIndex]
+                fBRight = bRuns[bIndex + 1]
+                fARuns = aIndex + 2
+                fBRuns = bIndex + 2
+            }
+
+            fun done(): Boolean {
+                require(fALeft <= SkRegion_kRunTypeSentinel)
+                require(fBLeft <= SkRegion_kRunTypeSentinel)
+                return fALeft == SkRegion_kRunTypeSentinel && fBLeft == SkRegion_kRunTypeSentinel
+            }
+
+            fun next() {
+                val inside: Int
+                val left: Int
+                var right = 0
+                var aFlush = false
+                var bFlush = false
+
+                var aLeft = fALeft
+                var aRight = fARight
+                var bLeft = fBLeft
+                var bRight = fBRight
+
+                if (aLeft < bLeft) {
+                    inside = 1
+                    left = aLeft
+                    if (aRight <= bLeft) { // [...] <...>
+                        right = aRight
+                        aFlush = true
+                    } else { // [...<..]...> or [...<...>...]
+                        aLeft = bLeft
+                        right = bLeft
+                    }
+                } else if (bLeft < aLeft) {
+                    inside = 2
+                    left = bLeft
+                    if (bRight <= aLeft) { // [...] <...>
+                        right = bRight
+                        bFlush = true
+                    } else { // [...<..]...> or [...<...>...]
+                        bLeft = aLeft
+                        right = aLeft
+                    }
+                } else { // a_left == b_left
+                    inside = 3
+                    left = aLeft // or b_left
+                    if (aRight <= bRight) {
+                        bLeft = aRight
+                        right = aRight
+                        aFlush = true
+                    }
+                    if (bRight <= aRight) {
+                        aLeft = bRight
+                        right = bRight
+                        bFlush = true
+                    }
+                }
+
+                if (aFlush) {
+                    aLeft = aRuns[fARuns]
+                    fARuns++
+                    aRight = aRuns[fARuns]
+                    fARuns++
+                }
+                if (bFlush) {
+                    bLeft = bRuns[fBRuns]
+                    fBRuns++
+                    bRight = bRuns[fBRuns]
+                    fBRuns++
+                }
+
+                require(left <= right)
+
+                // now update our state
+                fALeft = aLeft
+                fARight = aRight
+                fBLeft = bLeft
+                fBRight = bRight
+
+                fLeft = left
+                fRight = right
+                fInside = inside
+            }
+        }
+
+        private fun operateOnSpan(
+            a_runs: Array<Int>,
+            b_runs: Array<Int>,
+            a_run_index: Int,
+            b_run_index: Int,
+            array: RunArray,
+            dstOffset: Int,
+            min: Int,
+            max: Int
+        ): Int {
+            // This is a worst-case for this span plus two for TWO terminating sentinels.
+            array.resizeToAtLeast(
+                dstOffset +
+                    distanceToSentinel(a_runs, a_run_index) +
+                    distanceToSentinel(b_runs, b_run_index) +
+                    2
+            )
+            var dstIndex = dstOffset
+
+            val rec = SpanRect(a_runs, b_runs, a_run_index, b_run_index)
+            var firstInterval = true
+
+            while (!rec.done()) {
+                rec.next()
+
+                val left = rec.fLeft
+                val right = rec.fRight
+
+                // add left,right to our dst buffer (checking for coincidence
+                if (
+                    (rec.fInside - min).toUInt() <= (max - min).toUInt() && left < right
+                ) { // skip if equal
+                    if (firstInterval || array[dstIndex - 1] < left) {
+                        array[dstIndex] = left
+                        dstIndex++
+                        array[dstIndex] = right
+                        dstIndex++
+                        firstInterval = false
+                    } else {
+                        // update the right edge
+                        array[dstIndex - 1] = right
+                    }
+                }
+            }
+
+            array[dstIndex] = SkRegion_kRunTypeSentinel
+            dstIndex++
+            return dstIndex // dst - &(*array)[0]
+        }
+
+        private fun distanceToSentinel(runs: Array<Int>, startIndex: Int): Int {
+            var index = startIndex
+            if (runs.size <= index) {
+                println("We fucked up...")
+            }
+            while (runs[index] != SkRegion_kRunTypeSentinel) {
+                if (runs.size <= index + 2) {
+                    println("We fucked up...")
+                    return 256
+                }
+                index += 2
+            }
+            return index - startIndex
+        }
+    }
+
+    private fun operate(
+        aRuns: Array<Int>,
+        bRuns: Array<Int>,
+        dst: RunArray,
+        op: Op,
+        _aRunsIndex: Int = 0,
+        _bRunsIndex: Int = 0
+    ): Int {
+        var aRunsIndex = _aRunsIndex
+        var bRunsIndex = _bRunsIndex
+
+        var aTop = aRuns[aRunsIndex]
+        aRunsIndex++
+        var aBot = aRuns[aRunsIndex]
+        aRunsIndex++
+        var bTop = bRuns[bRunsIndex]
+        bRunsIndex++
+        var bBot = bRuns[bRunsIndex]
+        bRunsIndex++
+
+        aRunsIndex++ // skip the intervalCount
+        bRunsIndex++ // skip the intervalCount
+
+        val gEmptyScanline: Array<Int> =
+            arrayOf(
+                0, // fake bottom value
+                0, // zero intervals
+                SkRegion_kRunTypeSentinel,
+                // just need a 2nd value, since spanRec.init() reads 2 values, even
+                // though if the first value is the sentinel, it ignores the 2nd value.
+                // w/o the 2nd value here, we might read uninitialized memory.
+                // This happens when we are using gSentinel, which is pointing at
+                // our sentinel value.
+                0
+            )
+        val gSentinel = 2
+
+        // Now aRuns and bRuns to their intervals (or sentinel)
+
+        assertSentinel(aTop, false)
+        assertSentinel(aBot, false)
+        assertSentinel(bTop, false)
+        assertSentinel(bBot, false)
+
+        val oper = RgnOper(min(aTop, bTop), dst, op)
+
+        var prevBot = SkRegion_kRunTypeSentinel // so we fail the first test
+
+        while (aBot < SkRegion_kRunTypeSentinel || bBot < SkRegion_kRunTypeSentinel) {
+            var top: Int
+            var bot = 0
+
+            var run0 = gEmptyScanline
+            var run0Index = gSentinel
+            var run1 = gEmptyScanline
+            var run1Index = gSentinel
+            var aFlush = false
+            var bFlush = false
+
+            if (aTop < bTop) {
+                top = aTop
+                run0 = aRuns
+                run0Index = aRunsIndex
+                if (aBot <= bTop) { // [...] <...>
+                    bot = aBot
+                    aFlush = true
+                } else { // [...<..]...> or [...<...>...]
+                    aTop = bTop
+                    bot = bTop
+                }
+            } else if (bTop < aTop) {
+                top = bTop
+                run1 = bRuns
+                run1Index = bRunsIndex
+                if (bBot <= aTop) { // [...] <...>
+                    bot = bBot
+                    bFlush = true
+                } else { // [...<..]...> or [...<...>...]
+                    bTop = aTop
+                    bot = aTop
+                }
+            } else { // aTop == bTop
+                top = aTop // or bTop
+                run0 = aRuns
+                run0Index = aRunsIndex
+                run1 = bRuns
+                run1Index = bRunsIndex
+                if (aBot <= bBot) {
+                    bTop = aBot
+                    bot = aBot
+                    aFlush = true
+                }
+                if (bBot <= aBot) {
+                    aTop = bBot
+                    bot = bBot
+                    bFlush = true
+                }
+            }
+
+            if (top > prevBot) {
+                oper.addSpan(top, gEmptyScanline, gEmptyScanline, gSentinel, gSentinel)
+            }
+            oper.addSpan(bot, run0, run1, run0Index, run1Index)
+
+            if (aFlush) {
+                aRunsIndex = skipIntervals(aRuns, aRunsIndex)
+                aTop = aBot
+                aBot = aRuns[aRunsIndex]
+                aRunsIndex++ // skip to next index
+                aRunsIndex++ // skip uninitialized intervalCount
+                if (aBot == SkRegion_kRunTypeSentinel) {
+                    aTop = aBot
+                }
+            }
+            if (bFlush) {
+                bRunsIndex = skipIntervals(bRuns, bRunsIndex)
+                bTop = bBot
+                bBot = bRuns[bRunsIndex]
+                bRunsIndex++ // skip to next index
+                bRunsIndex++ // skip uninitialized intervalCount
+                if (bBot == SkRegion_kRunTypeSentinel) {
+                    bTop = bBot
+                }
+            }
+
+            prevBot = bot
+        }
+
+        return oper.flush()
+    }
+
+    private fun skipIntervals(runs: Array<Int>, index: Int): Int {
+        val intervals = runs[index - 1]
+        return index + intervals * 2 + 1
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified region. Return true if the result
+     * of the op is not empty.
+     */
+    @JsName("opRegion")
+    fun op(region: Region, op: Op): Boolean {
+        return op(this, region, op)
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified rect. Return true if the result of
+     * the op is not empty.
+     */
+    @JsName("op")
+    fun op(left: Int, top: Int, right: Int, bottom: Int, op: Op): Boolean {
+        return op(Rect.withoutCache(left, top, right, bottom), op)
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified rect. Return true if the result of
+     * the op is not empty.
+     */
+    @JsName("opRect")
+    fun op(r: Rect, op: Op): Boolean {
+        return op(from(r), op)
+    }
+
+    /**
+     * Set this region to the result of performing the Op on the specified rect and region. Return
+     * true if the result is not empty.
+     */
+    @JsName("opAndSetRegion")
+    fun op(rect: Rect, region: Region, op: Op): Boolean {
+        return op(from(rect), region, op)
+    }
+
+    @JsName("minus")
+    fun minus(other: Region): Region {
+        val thisRegion = from(this)
+        thisRegion.op(other, Op.XOR)
+        return thisRegion
+    }
+
+    @JsName("coversAtMost")
+    fun coversAtMost(testRegion: Region): Boolean {
+        val testRect = testRegion.bounds
+        val intersection = from(this)
+        return intersection.op(testRect, Op.INTERSECT) && !intersection.op(this, Op.XOR)
+    }
+
+    @JsName("coversAtLeast")
+    fun coversAtLeast(testRegion: Region): Boolean {
+        val intersection = from(this)
+        return intersection.op(testRegion, Op.INTERSECT) && !intersection.op(testRegion, Op.XOR)
+    }
+
+    @JsName("coversMoreThan")
+    fun coversMoreThan(testRegion: Region): Boolean {
+        return coversAtLeast(testRegion) && from(this).minus(testRegion).isNotEmpty
+    }
+
+    @JsName("outOfBoundsRegion")
+    fun outOfBoundsRegion(testRegion: Region): Region {
+        val testRect = testRegion.bounds
+        val outOfBoundsRegion = from(this)
+        outOfBoundsRegion.op(testRect, Op.INTERSECT) && outOfBoundsRegion.op(this, Op.XOR)
+        return outOfBoundsRegion
+    }
+
+    @JsName("uncoveredRegion")
+    fun uncoveredRegion(testRegion: Region): Region {
+        val uncoveredRegion = from(this)
+        uncoveredRegion.op(testRegion, Op.INTERSECT) && uncoveredRegion.op(testRegion, Op.XOR)
+        return uncoveredRegion
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Region
+            get() = Region()
+
+        private const val SkRegion_kRunTypeSentinel = 0x7FFFFFFF
+
+        private const val kRectRegionRuns = 7
+
+        private class MinMax(val min: Int, val max: Int)
+
+        private val gOpMinMax =
+            mapOf(
+                Op.DIFFERENCE to MinMax(1, 1),
+                Op.INTERSECT to MinMax(3, 3),
+                Op.UNION to MinMax(1, 3),
+                Op.XOR to MinMax(1, 2)
+            )
+
+        @JsName("from")
+        fun from(left: Int, top: Int, right: Int, bottom: Int): Region =
+            from(Rect.withoutCache(left, top, right, bottom))
+
+        @JsName("fromRegion") fun from(region: Region): Region = Region().also { it.set(region) }
+
+        @JsName("fromRect")
+        fun from(rect: Rect? = null): Region =
+            Region().also {
+                it.fRunHead = null
+                it.setRect(rect ?: Rect.EMPTY)
+            }
+
+        @JsName("fromRectF") fun from(rect: RectF?): Region = from(rect?.toRect())
+
+        @JsName("fromEmpty") fun from(): Region = from(Rect.EMPTY)
+
+        private fun skRegionValueIsSentinel(value: Int): Boolean {
+            return value == SkRegion_kRunTypeSentinel
+        }
+
+        private fun assertSentinel(value: Int, isSentinel: Boolean) {
+            require(skRegionValueIsSentinel(value) == isSentinel)
+        }
+
+        private fun getRectsFromString(regionString: String): Array<Rect> {
+            val rects = mutableListOf<Rect>()
+
+            if (regionString == "SkRegion()") {
+                return rects.toTypedArray()
+            }
+
+            var nativeRegionString = regionString.replace("SkRegion", "")
+            nativeRegionString = nativeRegionString.substring(2, nativeRegionString.length - 2)
+            nativeRegionString = nativeRegionString.replace(")(", ",")
+
+            var rect = Rect.EMPTY
+            for ((i, coord) in nativeRegionString.split(",").withIndex()) {
+                when (i % 4) {
+                    0 -> rect = Rect.withoutCache(coord.toInt(), 0, 0, 0)
+                    1 -> rect = Rect.withoutCache(rect.left, coord.toInt(), 0, 0)
+                    2 -> rect = Rect.withoutCache(rect.left, rect.top, coord.toInt(), 0)
+                    3 -> {
+                        rect = Rect.withoutCache(rect.left, rect.top, rect.right, coord.toInt())
+                        rects.add(rect)
+                    }
+                }
+            }
+
+            return rects.toTypedArray()
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/Size.kt b/libraries/flicker/src/android/tools/common/datatypes/Size.kt
new file mode 100644
index 0000000..ecc4068
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/Size.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.datatypes
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for SizeProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+open class Size
+protected constructor(@JsName("width") val width: Int, @JsName("height") val height: Int) {
+    @JsName("isEmpty")
+    val isEmpty: Boolean
+        get() = height == 0 || width == 0
+
+    @JsName("isNotEmpty")
+    val isNotEmpty: Boolean
+        get() = !isEmpty
+
+    open fun prettyPrint(): String = "$width x $height"
+
+    override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
+
+    override fun equals(other: Any?): Boolean =
+        other is Size && other.height == height && other.width == width
+
+    override fun hashCode(): Int {
+        var result = width
+        result = 31 * result + height
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Size
+            get() = withCache { Size(width = 0, height = 0) }
+        @JsName("from") fun from(width: Int, height: Int): Size = withCache { Size(width, height) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/ComponentName.kt b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentName.kt
new file mode 100644
index 0000000..1d50cac
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentName.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.datatypes.component
+
+import kotlin.js.JsName
+
+/**
+ * Create a new component identifier.
+ *
+ * This is a version of Android's ComponentName class for flicker. This is necessary because flicker
+ * codebase it also compiled into KotlinJS for use into Winscope
+ *
+ * @param packageName The name of the package that the component exists in. Can not be null.
+ * @param className The name of the class inside <var>pkg</var> that implements the component.
+ */
+data class ComponentName(override val packageName: String, override val className: String) :
+    IComponentName {
+    /**
+     * Obtains the activity name from the component name.
+     *
+     * See [ComponentName.toWindowName] for additional information
+     */
+    override fun toActivityName(): String {
+        return when {
+            packageName.isNotEmpty() && className.isNotEmpty() -> {
+                val sb = StringBuilder(packageName.length + className.length)
+                appendShortString(sb, packageName, className)
+                return sb.toString()
+            }
+            packageName.isNotEmpty() -> packageName
+            className.isNotEmpty() -> className
+            else -> error("Component name should have an activity of class name")
+        }
+    }
+
+    /**
+     * Obtains the window name from the component name.
+     *
+     * [ComponentName] builds the string representation as PKG/CLASS, however this doesn't work for
+     * system components such as IME, NavBar and StatusBar, Toast.
+     *
+     * If the component doesn't have a package name, assume it's a system component and return only
+     * the class name
+     */
+    override fun toWindowName(): String {
+        return when {
+            packageName.isNotEmpty() && className.isNotEmpty() -> "$packageName/$className"
+            packageName.isNotEmpty() -> packageName
+            className.isNotEmpty() -> className
+            else -> error("Component name should have an activity of class name")
+        }
+    }
+
+    @JsName("toShortWindowName")
+    private fun toShortWindowName(): String {
+        return when {
+            packageName.isNotEmpty() && className.isNotEmpty() ->
+                "$packageName/${className.removePrefix(packageName)}"
+            packageName.isNotEmpty() -> packageName
+            className.isNotEmpty() -> className
+            else -> error("Component name should have an activity of class name")
+        }
+    }
+
+    /**
+     * Obtains the layer name from the component name.
+     *
+     * See [toWindowName] for additional information
+     */
+    override fun toLayerName(): String {
+        var result = this.toWindowName()
+        if (result.contains("/") && !result.contains("#")) {
+            result = "$result#"
+        }
+
+        return result
+    }
+
+    @JsName("appendShortString")
+    private fun appendShortString(sb: StringBuilder, packageName: String, className: String) {
+        sb.append(packageName).append('/')
+        appendShortClassName(sb, packageName, className)
+    }
+
+    @JsName("appendShortClassName")
+    private fun appendShortClassName(sb: StringBuilder, packageName: String, className: String) {
+        if (className.startsWith(packageName)) {
+            val packageNameLength = packageName.length
+            val classNameLength = className.length
+            if (classNameLength > packageNameLength && className[packageNameLength] == '.') {
+                sb.append(className, packageNameLength, classNameLength)
+                return
+            }
+        }
+        sb.append(className)
+    }
+
+    @JsName("toActivityRecordFilter")
+    fun toActivityRecordFilter(): Regex =
+        Regex("ActivityRecord\\{.*${Regex.escape(this.toShortWindowName())}")
+
+    @JsName("toActivityNameRegex")
+    fun toActivityNameRegex(): Regex = Regex(".*${Regex.escape(this.toActivityName())}.*")
+
+    @JsName("toWindowNameRegex")
+    fun toWindowNameRegex(): Regex = Regex(".*${Regex.escape(this.toWindowName())}.*")
+
+    @JsName("toLayerNameRegex")
+    fun toLayerNameRegex(): Regex = Regex(".*${Regex.escape(this.toLayerName())}.*")
+
+    override fun toString(): String = toShortWindowName()
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/ComponentNameMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentNameMatcher.kt
new file mode 100644
index 0000000..81fdded
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentNameMatcher.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+import kotlin.js.JsName
+
+/** ComponentMatcher based on name */
+class ComponentNameMatcher(var component: ComponentName) : IComponentNameMatcher {
+    override val packageName: String
+        get() = component.packageName
+    override val className: String
+        get() = component.className
+    override fun toActivityName(): String = component.toActivityName()
+    override fun toWindowName(): String = component.toWindowName()
+    override fun toLayerName(): String = component.toLayerName()
+
+    constructor(
+        packageName: String,
+        className: String
+    ) : this(ComponentName(packageName, className))
+
+    constructor(className: String) : this("", className)
+
+    private fun <T> matchesAnyOf(
+        values: Array<T>,
+        valueProducer: (T) -> String,
+        regexProducer: (ComponentName) -> Regex,
+    ): Boolean {
+        val componentRegex = regexProducer.invoke(component)
+        val targets = values.map { valueProducer.invoke(it) }
+        return targets.any { value -> componentRegex.matches(value) }
+    }
+
+    override fun componentNameMatcherToString(): String {
+        return "ComponentNameMatcher(\"${this.packageName}\", " + "\"${this.className}\")"
+    }
+
+    /** {@inheritDoc} */
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean =
+        matchesAnyOf(windows, { it.title }, { it.toWindowNameRegex() })
+
+    /** {@inheritDoc} */
+    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean =
+        matchesAnyOf(activities, { it.name }, { it.toActivityNameRegex() })
+
+    /** {@inheritDoc} */
+    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean =
+        matchesAnyOf(layers, { it.name }, { it.toLayerNameRegex() })
+
+    /** {@inheritDoc} */
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
+
+    /** {@inheritDoc} */
+    override fun toActivityIdentifier(): String = component.toActivityName()
+
+    /** {@inheritDoc} */
+    override fun toWindowIdentifier(): String = component.toWindowName()
+
+    /** {@inheritDoc} */
+    override fun toLayerIdentifier(): String = component.toLayerName()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ComponentNameMatcher) return false
+        return component == other.component
+    }
+
+    override fun hashCode(): Int = component.hashCode()
+
+    override fun toString(): String = component.toString()
+
+    companion object {
+        @JsName("NAV_BAR") val NAV_BAR = ComponentNameMatcher("", "NavigationBar0")
+        @JsName("TASK_BAR") val TASK_BAR = ComponentNameMatcher("", "Taskbar")
+        @JsName("STATUS_BAR") val STATUS_BAR = ComponentNameMatcher("", "StatusBar")
+        @JsName("ROTATION") val ROTATION = ComponentNameMatcher("", "RotationLayer")
+        @JsName("BACK_SURFACE") val BACK_SURFACE = ComponentNameMatcher("", "BackColorSurface")
+        @JsName("IME") val IME = ComponentNameMatcher("", "InputMethod")
+        @JsName("IME_SNAPSHOT") val IME_SNAPSHOT = ComponentNameMatcher("", "IME-snapshot-surface")
+        @JsName("SPLASH_SCREEN") val SPLASH_SCREEN = ComponentNameMatcher("", "Splash Screen")
+        @JsName("SNAPSHOT") val SNAPSHOT = ComponentNameMatcher("", "SnapshotStartingWindow")
+        @JsName("TRANSITION_SNAPSHOT")
+        val TRANSITION_SNAPSHOT = ComponentNameMatcher("", "transition snapshot")
+        @JsName("LETTERBOX") val LETTERBOX = ComponentNameMatcher("", "Letterbox")
+        @JsName("WALLPAPER_BBQ_WRAPPER")
+        val WALLPAPER_BBQ_WRAPPER = ComponentNameMatcher("", "Wallpaper BBQ wrapper")
+        @JsName("PIP_CONTENT_OVERLAY")
+        val PIP_CONTENT_OVERLAY = ComponentNameMatcher("", "PipContentOverlay")
+        @JsName("LAUNCHER")
+        val LAUNCHER =
+            ComponentNameMatcher(
+                "com.google.android.apps.nexuslauncher",
+                "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+            )
+        @JsName("AOSP_LAUNCHER")
+        val AOSP_LAUNCHER =
+            ComponentNameMatcher(
+                "com.android.launcher3",
+                "com.android.launcher3.uioverrides.QuickstepLauncher"
+            )
+        @JsName("SPLIT_DIVIDER")
+        val SPLIT_DIVIDER = ComponentNameMatcher("", "StageCoordinatorSplitDivider")
+        @JsName("DEFAULT_TASK_DISPLAY_AREA")
+        val DEFAULT_TASK_DISPLAY_AREA = ComponentNameMatcher("", "DefaultTaskDisplayArea")
+
+        /**
+         * Creates a component matcher from a window or layer name.
+         *
+         * Requires the [str] to contain both the package and class name (with a / separator)
+         *
+         * @param str Value to parse
+         */
+        @JsName("unflattenFromString")
+        fun unflattenFromString(str: String): ComponentNameMatcher {
+            val sep = str.indexOf('/')
+            if (sep < 0 || sep + 1 >= str.length) {
+                error("Missing package/class separator")
+            }
+            val pkg = str.substring(0, sep)
+            var cls = str.substring(sep + 1)
+            if (cls.isNotEmpty() && cls[0] == '.') {
+                cls = pkg + cls
+            }
+            return ComponentNameMatcher(pkg, cls)
+        }
+
+        /**
+         * Creates a component matcher from a window or layer name. The name might contain junk,
+         * which will be removed to only extract package and class name (e.g. other words before
+         * package name, separated by spaces, #id in the end after the class name)
+         *
+         * Requires the [str] to contain both the package and class name (with a / separator)
+         *
+         * @param str Value to parse
+         */
+        @JsName("unflattenFromStringWithJunk")
+        fun unflattenFromStringWithJunk(str: String): ComponentNameMatcher {
+            val sep = str.indexOf('/')
+            if (sep < 0 || sep + 1 >= str.length) {
+                error("Missing package/class separator")
+            }
+
+            var pkg = str.substring(0, sep)
+            var pkgSep: Int = -1
+            val pkgCharArr = pkg.toCharArray()
+            for (index in (0..pkgCharArr.lastIndex).reversed()) {
+                val currentChar = pkgCharArr[index]
+                if (currentChar !in 'A'..'Z' && currentChar !in 'a'..'z' && currentChar != '.') {
+                    pkgSep = index
+                    break
+                }
+            }
+            if (!(pkgSep < 0 || pkgSep + 1 >= pkg.length)) {
+                pkg = pkg.substring(pkgSep, pkg.length)
+            }
+
+            var cls = str.substring(sep + 1)
+            var clsSep = -1 // cls.indexOf('#')
+            val clsCharArr = cls.toCharArray()
+            for (index in (0..clsCharArr.lastIndex)) {
+                val currentChar = clsCharArr[index]
+                if (currentChar !in 'A'..'Z' && currentChar !in 'a'..'z' && currentChar != '.') {
+                    clsSep = index
+                    break
+                }
+            }
+            if (!(clsSep < 0 || clsSep + 1 >= cls.length)) {
+                cls = cls.substring(0, clsSep)
+            }
+
+            if (cls.isNotEmpty() && cls[0] == '.') {
+                cls = pkg + cls
+            }
+            return ComponentNameMatcher(pkg, cls)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/ComponentSplashScreenMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentSplashScreenMatcher.kt
new file mode 100644
index 0000000..88f0f52
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/ComponentSplashScreenMatcher.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+
+class ComponentSplashScreenMatcher(val componentNameMatcher: ComponentNameMatcher) :
+    IComponentMatcher {
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
+        error("Unimplemented - There are no splashscreen windows")
+    }
+
+    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
+        error("Unimplemented - There are no splashscreen windows")
+    }
+
+    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
+        return layers.any {
+            if (!it.name.contains("Splash Screen")) {
+                return@any false
+            }
+            if (it.children.isNotEmpty()) {
+                // Not leaf splash screen layer but container of the splash screen layer
+                return@any false
+            }
+            val grandParent = it.parent?.parent
+            requireNotNull(grandParent) { "Splash screen layer's grandparent shouldn't be null" }
+            return@any componentNameMatcher.layerMatchesAnyOf(grandParent)
+        }
+    }
+
+    override fun toActivityIdentifier(): String {
+        error("Unimplemented - There are no splashscreen windows")
+    }
+
+    override fun toWindowIdentifier(): String {
+        error("Unimplemented - There are no splashscreen windows")
+    }
+
+    override fun toLayerIdentifier(): String {
+        return "Splash Screen ${componentNameMatcher.className}"
+    }
+
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean {
+        val splashScreenLayer = layers.filter { layerMatchesAnyOf(it) }
+        require(splashScreenLayer.size < 1) {
+            "More than on SplashScreen layer found. Only up to 1 match was expected."
+        }
+        return condition(splashScreenLayer)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/EdgeExtensionComponentMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/EdgeExtensionComponentMatcher.kt
new file mode 100644
index 0000000..a4ad74e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/EdgeExtensionComponentMatcher.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+
+class EdgeExtensionComponentMatcher : IComponentMatcher {
+    /** {@inheritDoc} */
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
+        // Doesn't have a window component only layers
+        return false
+    }
+
+    /** {@inheritDoc} */
+    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
+        // Doesn't have a window component only layers
+        return false
+    }
+
+    /** {@inheritDoc} */
+    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
+        return layers.any {
+            if (it.name.contains("bbq-wrapper")) {
+                val parent = it.parent ?: return false
+                return layerMatchesAnyOf(parent)
+            }
+
+            return it.name.contains("Left Edge Extension") ||
+                it.name.contains("Right Edge Extension")
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
+
+    /** {@inheritDoc} */
+    override fun toActivityIdentifier(): String {
+        throw NotImplementedError(
+            "toActivityIdentifier() is not implemented on EdgeExtensionComponentMatcher"
+        )
+    }
+
+    /** {@inheritDoc} */
+    override fun toWindowIdentifier(): String {
+        throw NotImplementedError(
+            "toWindowName() is not implemented on EdgeExtensionComponentMatcher"
+        )
+    }
+
+    /** {@inheritDoc} */
+    override fun toLayerIdentifier(): String {
+        return "EdgeExtensionLayer"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/ExactComponentIdMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/ExactComponentIdMatcher.kt
new file mode 100644
index 0000000..1e74756
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/ExactComponentIdMatcher.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+
+/**
+ * A component matcher that matches the targeted window and layer with ids windowId and layerId
+ * respectively.
+ *
+ * No other windows or layers will be matched, if you want to also match the children of that window
+ * and layer then use FullComponentIdMatcher instead.
+ */
+class ExactComponentIdMatcher(private val windowId: Int, private val layerId: Int) :
+    IComponentMatcher {
+    /**
+     * @return if any of the [components] matches any of [windows]
+     *
+     * @param windows to search
+     */
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>) =
+        windows.any { it.token == windowId.toString(16) }
+
+    /**
+     * @return if any of the [components] matches any of [activities]
+     *
+     * @param activities to search
+     */
+    override fun activityMatchesAnyOf(activities: Array<Activity>) =
+        activities.any { it.token == windowId.toString(16) }
+
+    /**
+     * @return if any of the [components] matches any of [layers]
+     *
+     * @param layers to search
+     */
+    override fun layerMatchesAnyOf(layers: Array<Layer>) = layers.any { it.id == layerId }
+
+    /** {@inheritDoc} */
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean = condition(layers.filter { it.id == layerId })
+
+    /** {@inheritDoc} */
+    override fun toActivityIdentifier() = toWindowIdentifier()
+
+    /** {@inheritDoc} */
+    override fun toWindowIdentifier() = "Window#${windowId.toString(16)}"
+
+    /** {@inheritDoc} */
+    override fun toLayerIdentifier(): String = "Layer#$layerId"
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/FullComponentIdMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/FullComponentIdMatcher.kt
new file mode 100644
index 0000000..c29f272
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/FullComponentIdMatcher.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+
+/**
+ * A component matcher that matches the targeted window and layer with ids windowId and layerId
+ * respectively and all their children windows and layers.
+ *
+ * If you want to only match the window and layer with the specified ids then use
+ * ExactComponentIdMatcher instead.
+ */
+class FullComponentIdMatcher(val windowId: Int, val layerId: Int) : IComponentMatcher {
+    /**
+     * @return if any of the [components] matches any of [windows]
+     *
+     * @param windows to search
+     */
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean =
+        windows.any {
+            val parent = it.parent
+            when {
+                it.token == windowId.toString(16) -> true
+                parent != null -> windowMatchesAnyOf(parent)
+                else -> false
+            }
+        }
+
+    /**
+     * @return if any of the [components] matches any of [activities]
+     *
+     * @param activities to search
+     */
+    override fun activityMatchesAnyOf(activities: Array<Activity>) =
+        activities.any { it.token == windowId.toString(16) }
+
+    /**
+     * @return if any of the [components] matches any of [layers]
+     *
+     * @param layers to search
+     */
+    override fun layerMatchesAnyOf(layers: Array<Layer>) =
+        layers.any {
+            val parent = it.parent
+            when {
+                it.id == layerId -> true
+                parent != null -> layerMatchesAnyOf(parent)
+                else -> false
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
+
+    /** {@inheritDoc} */
+    override fun toActivityIdentifier() = toWindowIdentifier()
+
+    /** {@inheritDoc} */
+    override fun toWindowIdentifier() = "Window#${windowId.toString(16)} & children"
+
+    /** {@inheritDoc} */
+    override fun toLayerIdentifier(): String = "Layer#$layerId & children"
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/IComponentMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentMatcher.kt
new file mode 100644
index 0000000..edbf55c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentMatcher.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+interface IComponentMatcher {
+    @JsName("or")
+    fun or(other: IComponentMatcher): IComponentMatcher {
+        return OrComponentMatcher(arrayOf(this, other))
+    }
+
+    /**
+     * @return if any of the [components] matches [window]
+     *
+     * @param window to search
+     */
+    @JsName("windowMatchesAnyOf")
+    fun windowMatchesAnyOf(window: WindowContainer): Boolean = windowMatchesAnyOf(arrayOf(window))
+
+    /**
+     * @return if any of the [components] matches any of [windows]
+     *
+     * @param windows to search
+     */
+    @JsName("windowMatchesAnyOfCollection")
+    fun windowMatchesAnyOf(windows: Collection<WindowContainer>): Boolean =
+        windowMatchesAnyOf(windows.toTypedArray())
+
+    /**
+     * @return if any of the [windows] fit the matching conditions of the matcher
+     *
+     * @param windows to search
+     */
+    @JsName("windowMatchesAnyOfArray")
+    fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean
+
+    /**
+     * @return if any of the [components] matches [activity]
+     *
+     * @param activity to search
+     */
+    @JsName("activityMatchesAnyOf")
+    fun activityMatchesAnyOf(activity: Activity): Boolean = activityMatchesAnyOf(arrayOf(activity))
+
+    /**
+     * @return if any of the [components] matches any of [activities]
+     *
+     * @param activities to search
+     */
+    @JsName("activityMatchesAnyOfCollection")
+    fun activityMatchesAnyOf(activities: Collection<Activity>): Boolean =
+        activityMatchesAnyOf(activities.toTypedArray())
+
+    /**
+     * @return if any of the [components] matches any of [activities]
+     *
+     * @param activities to search
+     */
+    @JsName("activityMatchesAnyOfArray")
+    fun activityMatchesAnyOf(activities: Array<Activity>): Boolean
+
+    /**
+     * @return if any of the [components] matches [layer]
+     *
+     * @param layer to search
+     */
+    @JsName("layerMatchesAnyOf")
+    fun layerMatchesAnyOf(layer: Layer): Boolean = layerMatchesAnyOf(arrayOf(layer))
+
+    /**
+     * @return if any of the [components] matches any of [layers]
+     *
+     * @param layers to search
+     */
+    @JsName("layerMatchesAnyOfCollection")
+    fun layerMatchesAnyOf(layers: Collection<Layer>): Boolean =
+        layerMatchesAnyOf(layers.toTypedArray())
+
+    @JsName("layerMatchesAnyOfArray") fun layerMatchesAnyOf(layers: Array<Layer>): Boolean
+
+    /**
+     * @return an identifier string that provides enough information to determine which activities
+     * ```
+     *         the matcher is looking to match. Mostly used for debugging purposes in error messages
+     * ```
+     */
+    fun toActivityIdentifier(): String
+
+    /**
+     * @return an identifier string that provides enough information to determine which windows the
+     * ```
+     *         matcher is looking to match. Mostly used for debugging purposes in error messages.
+     * ```
+     */
+    @JsName("toWindowIdentifier") fun toWindowIdentifier(): String
+
+    /**
+     * @return an identifier string that provides enough information to determine which layers the
+     * ```
+     *         matcher is looking to match. Mostly used for debugging purposes in error messages.
+     * ```
+     */
+    @JsName("toLayerIdentifier") fun toLayerIdentifier(): String
+
+    /**
+     * @param layers Collection of layers check for matches
+     * @param condition A function taking the matched layers of a base level component and returning
+     * ```
+     *              true or false base on if the check succeeded.
+     * @return
+     * ```
+     * true iff all the check condition is satisfied according to the ComponentMatcher's
+     * ```
+     *         defined execution of it.
+     * ```
+     */
+    @JsName("check")
+    fun check(layers: Collection<Layer>, condition: (Collection<Layer>) -> Boolean): Boolean
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/IComponentName.kt b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentName.kt
new file mode 100644
index 0000000..fc98f8a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentName.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.datatypes.component
+
+import kotlin.js.JsName
+
+interface IComponentName {
+    @JsName("packageName") val packageName: String
+    @JsName("className") val className: String
+    @JsName("toActivityName") fun toActivityName(): String
+    @JsName("toWindowName") fun toWindowName(): String
+    @JsName("toLayerName") fun toLayerName(): String
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/IComponentNameMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentNameMatcher.kt
new file mode 100644
index 0000000..8b5dd90
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/IComponentNameMatcher.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.datatypes.component
+
+interface IComponentNameMatcher : IComponentMatcher, IComponentName {
+    fun componentNameMatcherToString(): String
+}
diff --git a/libraries/flicker/src/android/tools/common/datatypes/component/OrComponentMatcher.kt b/libraries/flicker/src/android/tools/common/datatypes/component/OrComponentMatcher.kt
new file mode 100644
index 0000000..dae8cd4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/datatypes/component/OrComponentMatcher.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.datatypes.component
+
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowContainer
+
+class OrComponentMatcher(private val componentMatchers: Array<out IComponentMatcher>) :
+    IComponentMatcher {
+
+    /** {@inheritDoc} */
+    override fun windowMatchesAnyOf(window: WindowContainer): Boolean {
+        return componentMatchers.any { it.windowMatchesAnyOf(window) }
+    }
+
+    /** {@inheritDoc} */
+    override fun windowMatchesAnyOf(windows: Collection<WindowContainer>): Boolean {
+        return componentMatchers.any { it.windowMatchesAnyOf(windows) }
+    }
+
+    /** {@inheritDoc} */
+    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
+        return componentMatchers.any { it.windowMatchesAnyOf(windows) }
+    }
+
+    /** {@inheritDoc} */
+    override fun activityMatchesAnyOf(activity: Activity): Boolean {
+        return componentMatchers.any { it.activityMatchesAnyOf(activity) }
+    }
+
+    /** {@inheritDoc} */
+    override fun activityMatchesAnyOf(activities: Collection<Activity>): Boolean {
+        return componentMatchers.any { it.activityMatchesAnyOf(activities) }
+    }
+
+    /** {@inheritDoc} */
+    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
+        return componentMatchers.any { it.activityMatchesAnyOf(activities) }
+    }
+
+    /** {@inheritDoc} */
+    override fun layerMatchesAnyOf(layer: Layer): Boolean {
+        return componentMatchers.any { it.layerMatchesAnyOf(layer) }
+    }
+
+    /** {@inheritDoc} */
+    override fun layerMatchesAnyOf(layers: Collection<Layer>): Boolean {
+        return componentMatchers.any { it.layerMatchesAnyOf(layers) }
+    }
+
+    /** {@inheritDoc} */
+    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
+        return componentMatchers.any { it.layerMatchesAnyOf(layers) }
+    }
+
+    /** {@inheritDoc} */
+    override fun check(
+        layers: Collection<Layer>,
+        condition: (Collection<Layer>) -> Boolean
+    ): Boolean {
+        return componentMatchers.any { oredComponent ->
+            oredComponent.check(layers) { condition(it) }
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun toActivityIdentifier(): String =
+        componentMatchers.joinToString(" or ") { it.toActivityIdentifier() }
+
+    /** {@inheritDoc} */
+    override fun toWindowIdentifier(): String =
+        componentMatchers.joinToString(" or ") { it.toWindowIdentifier() }
+
+    /** {@inheritDoc} */
+    override fun toLayerIdentifier(): String =
+        componentMatchers.joinToString(" or ") { it.toLayerIdentifier() }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/AssertionInvocationGroup.kt b/libraries/flicker/src/android/tools/common/flicker/AssertionInvocationGroup.kt
new file mode 100644
index 0000000..09812bd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/AssertionInvocationGroup.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+enum class AssertionInvocationGroup {
+    BLOCKING,
+    NON_BLOCKING
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/IFlickerService.kt b/libraries/flicker/src/android/tools/common/flicker/IFlickerService.kt
new file mode 100644
index 0000000..07a247c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/IFlickerService.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.io.IReader
+
+interface IFlickerService {
+    fun process(reader: IReader): List<IAssertionResult>
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/IScenarioInstance.kt b/libraries/flicker/src/android/tools/common/flicker/IScenarioInstance.kt
new file mode 100644
index 0000000..09b0c47
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/IScenarioInstance.kt
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+import android.tools.common.IScenario
+import android.tools.common.flicker.config.FaasScenarioType
+import android.tools.common.io.IReader
+import android.tools.common.traces.wm.Transition
+
+interface IScenarioInstance : IScenario {
+    val type: FaasScenarioType
+    // A reader to read the part of the trace associated with the scenario instance
+    val reader: IReader
+
+    val associatedTransition: Transition?
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/ITracesCollector.kt b/libraries/flicker/src/android/tools/common/flicker/ITracesCollector.kt
new file mode 100644
index 0000000..c531d28
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/ITracesCollector.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+import android.tools.common.io.IReader
+
+interface ITracesCollector {
+    fun start()
+    fun stop()
+    fun getResultReader(): IReader
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/ScenarioInstance.kt b/libraries/flicker/src/android/tools/common/flicker/ScenarioInstance.kt
new file mode 100644
index 0000000..6df8663
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/ScenarioInstance.kt
@@ -0,0 +1,52 @@
+/*
+ * 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
+
+import android.tools.common.Rotation
+import android.tools.common.Timestamp
+import android.tools.common.flicker.config.FaasScenarioType
+import android.tools.common.io.IReader
+import android.tools.common.traces.events.CujType
+import android.tools.common.traces.wm.Transition
+
+data class ScenarioInstance(
+    override val type: FaasScenarioType,
+    override val startRotation: Rotation,
+    override val endRotation: Rotation,
+    val startTimestamp: Timestamp,
+    val endTimestamp: Timestamp,
+    override val reader: IReader,
+    val associatedCuj: CujType? = null,
+    override val associatedTransition: Transition? = null,
+) : IScenarioInstance {
+    val startTransaction
+        get() = associatedTransition?.startTransaction
+    val finishTransaction
+        get() = associatedTransition?.finishTransaction
+
+    // b/227752705
+    override val navBarMode
+        get() = error("Unsupported")
+
+    override val key = "${type.name}_${startRotation}_$endRotation"
+
+    override val description = key
+
+    override val isEmpty = false
+
+    override fun toString() = key
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/TagIdGenerator.kt b/libraries/flicker/src/android/tools/common/flicker/TagIdGenerator.kt
new file mode 100644
index 0000000..154e5e3
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/TagIdGenerator.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+class TagIdGenerator {
+    companion object {
+        fun getNext() = ++latestId
+        private var latestId = 0
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionData.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionData.kt
new file mode 100644
index 0000000..2d68ff6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionData.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.assertions
+
+import android.tools.common.flicker.subject.FlickerSubject
+import kotlin.reflect.KClass
+
+/** Class containing basic data about an assertion */
+data class AssertionData(
+    /** Segment of the trace where the assertion will be applied (e.g., start, end). */
+    val tag: String,
+    /** Expected run result type */
+    val expectedSubjectClass: KClass<out FlickerSubject>,
+    /** Assertion command */
+    val assertion: FlickerSubject.() -> Unit
+) {
+    /**
+     * Extracts the data from the result and executes the assertion
+     *
+     * @param run Run to be asserted
+     */
+    fun checkAssertion(run: SubjectsParser) {
+        val subjects = run.getSubjects(tag).filter { expectedSubjectClass.isInstance(it) }
+        if (subjects.isEmpty()) {
+            return
+        }
+        subjects.forEach { it.run { assertion(this) } }
+    }
+
+    override fun toString(): String = buildString {
+        append("AssertionData(tag='")
+        append(tag)
+        append("', expectedSubjectClass='")
+        append(expectedSubjectClass.simpleName)
+        append("', assertion='")
+        append(assertion)
+        append(")")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionsChecker.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionsChecker.kt
new file mode 100644
index 0000000..365b8de
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/AssertionsChecker.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.assertions
+
+import android.tools.common.flicker.subject.FlickerSubject
+import kotlin.math.max
+
+/**
+ * Runs sequences of assertions on sequences of subjects.
+ *
+ * Starting at the first assertion and first trace entry, executes the assertions iteratively on the
+ * trace until all assertions and trace entries succeed.
+ *
+ * @param <T> trace entry type </T>
+ */
+class AssertionsChecker<T : FlickerSubject> {
+    private val assertions = mutableListOf<CompoundAssertion<T>>()
+    private var skipUntilFirstAssertion = false
+
+    internal fun isEmpty() = assertions.isEmpty()
+
+    /** Add [assertion] to a new [CompoundAssertion] block. */
+    fun add(name: String, isOptional: Boolean = false, assertion: (T) -> Unit) {
+        assertions.add(CompoundAssertion(assertion, name, isOptional))
+    }
+
+    /** Append [assertion] to the last existing set of assertions. */
+    fun append(name: String, isOptional: Boolean = false, assertion: (T) -> Unit) {
+        assertions.last().add(assertion, name, isOptional)
+    }
+
+    /**
+     * Steps through each trace entry checking if provided assertions are true in the order they are
+     * added. Each assertion must be true for at least a single trace entry.
+     *
+     * This can be used to check for asserting a change in property over a trace. Such as visibility
+     * for a window changes from true to false or top-most window changes from A to B and back to A
+     * again.
+     *
+     * It is also possible to ignore failures on initial elements, until the first assertion passes,
+     * this allows the trace to be recorded for longer periods, and the checks to happen only after
+     * some time.
+     *
+     * @param entries list of entries to perform assertions on
+     * @return list of failed assertion results
+     */
+    fun test(entries: List<T>) {
+        if (assertions.isEmpty() || entries.isEmpty()) {
+            return
+        }
+
+        var entryIndex = 0
+        var assertionIndex = 0
+        var lastPassedAssertionIndex = -1
+        val assertionTrace = mutableListOf<String>()
+        while (assertionIndex < assertions.size && entryIndex < entries.size) {
+            val currentAssertion = assertions[assertionIndex]
+            val currEntry = entries[entryIndex]
+            try {
+                val log =
+                    "${assertionIndex + 1}/${assertions.size}:[${currentAssertion.name}]\t" +
+                        "Entry: ${entryIndex + 1}/${entries.size} $currEntry"
+                assertionTrace.add(log)
+                currentAssertion.invoke(currEntry)
+                lastPassedAssertionIndex = assertionIndex
+                entryIndex++
+            } catch (e: Throwable) {
+                // ignore errors at the start of the trace
+                val ignoreFailure = skipUntilFirstAssertion && lastPassedAssertionIndex == -1
+                if (ignoreFailure) {
+                    entryIndex++
+                    continue
+                }
+                // failure is an optional assertion, just consider it passed skip it
+                if (currentAssertion.isOptional) {
+                    lastPassedAssertionIndex = assertionIndex
+                    assertionIndex++
+                    continue
+                }
+                if (lastPassedAssertionIndex != assertionIndex) {
+                    val prevEntry = entries[max(entryIndex - 1, 0)]
+                    prevEntry.fail(e)
+                }
+                assertionIndex++
+                if (assertionIndex == assertions.size) {
+                    val prevEntry = entries[max(entryIndex - 1, 0)]
+                    prevEntry.fail(e)
+                }
+            }
+        }
+        // Didn't pass any assertions
+        if (lastPassedAssertionIndex == -1 && assertions.isNotEmpty()) {
+            entries.first().fail("Assertion never passed", assertions.first())
+        }
+
+        val untestedAssertions = assertions.drop(assertionIndex + 1)
+        if (untestedAssertions.any { !it.isOptional }) {
+            val passedAssertionsFacts = assertions.take(assertionIndex).map { Fact("Passed", it) }
+            val untestedAssertionsFacts = untestedAssertions.map { Fact("Untested", it) }
+            val trace = assertionTrace.map { Fact("Trace", it) }
+            val reason = mutableListOf<Fact>()
+            reason.addAll(passedAssertionsFacts)
+            reason.add(Fact("Assertion never failed", assertions[assertionIndex]))
+            reason.addAll(untestedAssertionsFacts)
+            reason.addAll(trace)
+            entries.first().fail(reason)
+        }
+    }
+
+    /**
+     * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
+     * end of the trace without passing any assertion, return a failure with the name/reason from
+     * the first assertion
+     */
+    fun skipUntilFirstAssertion() {
+        skipUntilFirstAssertion = true
+    }
+
+    fun isEqual(other: Any?): Boolean {
+        if (
+            other !is AssertionsChecker<*> ||
+                skipUntilFirstAssertion != other.skipUntilFirstAssertion
+        ) {
+            return false
+        }
+        assertions.forEachIndexed { index, assertion ->
+            if (assertion != other.assertions[index]) {
+                return false
+            }
+        }
+        return true
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/CompoundAssertion.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/CompoundAssertion.kt
new file mode 100644
index 0000000..277628b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/CompoundAssertion.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.assertions
+
+/** Utility class to store assertions composed of multiple individual assertions */
+class CompoundAssertion<T>(assertion: (T) -> Unit, name: String, optional: Boolean) :
+    IAssertion<T> {
+    private val assertions = mutableListOf<NamedAssertion<T>>()
+
+    init {
+        add(assertion, name, optional)
+    }
+
+    override val isOptional
+        get() = assertions.all { it.isOptional }
+
+    override val name
+        get() = assertions.joinToString(" and ") { it.name }
+
+    /**
+     * Executes all [assertions] on [target]
+     *
+     * In case of failure, returns the first non-optional failure (if available) or the first failed
+     * assertion
+     */
+    override fun invoke(target: T) {
+        val failures =
+            assertions.mapNotNull { assertion ->
+                val error = kotlin.runCatching { assertion.invoke(target) }.exceptionOrNull()
+                if (error != null) {
+                    Pair(assertion, error)
+                } else {
+                    null
+                }
+            }
+        val nonOptionalFailure = failures.firstOrNull { !it.first.isOptional }
+        if (nonOptionalFailure != null) {
+            throw nonOptionalFailure.second
+        }
+        val firstFailure = failures.firstOrNull()
+        // Only throw first failure if all siblings are also optional otherwise don't throw anything
+        // If the CompoundAssertion is fully optional (i.e. all assertions in the compound assertion
+        // are optional), then we want to make sure the AssertionsChecker knows about the failure to
+        // not advance to the next state. Otherwise, the AssertionChecker doesn't need to know about
+        // the failure and can just consider the assertion as passed and advance to the next state
+        // since there were non-optional assertions which passed.
+        if (firstFailure != null && isOptional) {
+            throw firstFailure.second
+        }
+    }
+
+    /** Adds a new assertion to the list */
+    fun add(assertion: (T) -> Unit, name: String, optional: Boolean) {
+        assertions.add(NamedAssertion(assertion, name, optional))
+    }
+
+    override fun toString(): String = name
+
+    override fun equals(other: Any?): Boolean {
+        if (other !is CompoundAssertion<*>) {
+            return false
+        }
+        if (!super.equals(other)) {
+            return false
+        }
+        assertions.forEachIndexed { index, assertion ->
+            if (assertion != other.assertions[index]) {
+                return false
+            }
+        }
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return assertions.hashCode()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/Fact.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/Fact.kt
new file mode 100644
index 0000000..75fc4dd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/Fact.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.assertions
+
+/** A string key-value pair in a failure message, such as "expected: abc" or "but was: xyz." */
+data class Fact(val key: String, val value: String) {
+
+    constructor(key: String, value: Any? = null) : this(key, "$value")
+
+    override fun toString(): String {
+        return if (value.isEmpty()) key else "$key: $value"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/IAssertion.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/IAssertion.kt
new file mode 100644
index 0000000..c8503d8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/IAssertion.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.assertions
+
+/**
+ * Checks assertion on a single trace entry.
+ *
+ * @param <T> trace entry type to perform the assertion on. </T>
+ */
+interface IAssertion<T> {
+    val isOptional: Boolean
+    val name: String
+    operator fun invoke(target: T)
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/NamedAssertion.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/NamedAssertion.kt
new file mode 100644
index 0000000..a4495a4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/NamedAssertion.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.assertions
+
+/**
+ * Utility class to store assertions with an identifier to help generate more useful debug data when
+ * dealing with multiple assertions.
+ *
+ * @param predicate Assertion to execute
+ * @param name Assertion name
+ * @param isOptional If the assertion is optional (can fail) or not (must pass)
+ */
+open class NamedAssertion<T>(
+    val predicate: (T) -> Unit,
+    override val name: String,
+    override val isOptional: Boolean = false
+) : IAssertion<T> {
+    override operator fun invoke(target: T) = predicate(target)
+    override fun toString(): String = "Assertion($name)${if (isOptional) "[optional]" else ""}"
+
+    /**
+     * We can't check the actual assertion is the same. We are checking for the name, which should
+     * have a 1:1 correspondence with the assertion, but there is no actual guarantee of the same
+     * execution of the assertion even if isEqual() is true.
+     */
+    override fun equals(other: Any?): Boolean {
+        if (other !is NamedAssertion<*>) {
+            return false
+        }
+        if (name != other.name) {
+            return false
+        }
+        if (isOptional != other.isOptional) {
+            return false
+        }
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = predicate.hashCode()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + isOptional.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertions/SubjectsParser.kt b/libraries/flicker/src/android/tools/common/flicker/assertions/SubjectsParser.kt
new file mode 100644
index 0000000..96a6ff8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertions/SubjectsParser.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.assertions
+
+import android.tools.common.Tag
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+import android.tools.common.io.IReader
+import android.tools.common.traces.events.FocusEvent
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+
+/**
+ * Helper class to read traces from a [resultReader] and parse them into subjects for assertion
+ *
+ * @param resultReader to read the result artifacts
+ */
+open class SubjectsParser(private val resultReader: IReader) {
+    fun getSubjects(tag: String): List<FlickerSubject> {
+        val result = mutableListOf<FlickerSubject>()
+
+        if (tag == Tag.ALL) {
+            wmTraceSubject?.let { result.add(it) }
+            layersTraceSubject?.let { result.add(it) }
+        } else {
+            getWmStateSubject(tag)?.let { result.add(it) }
+            getLayerTraceEntrySubject(tag)?.let { result.add(it) }
+        }
+        eventLogSubject?.let { result.add(it) }
+
+        return result
+    }
+
+    /** Truth subject that corresponds to a [WindowManagerTrace] */
+    private val wmTraceSubject: WindowManagerTraceSubject?
+        get() = doGetWmTraceSubject()
+
+    protected open fun doGetWmTraceSubject(): WindowManagerTraceSubject? {
+        val trace = resultReader.readWmTrace() ?: return null
+        return WindowManagerTraceSubject(trace)
+    }
+
+    /** Truth subject that corresponds to a [LayersTrace] */
+    private val layersTraceSubject: LayersTraceSubject?
+        get() = doGetLayersTraceSubject()
+
+    protected open fun doGetLayersTraceSubject(): LayersTraceSubject? {
+        val trace = resultReader.readLayersTrace() ?: return null
+        return LayersTraceSubject(trace)
+    }
+
+    /** Truth subject that corresponds to a [WindowManagerState] */
+    private fun getWmStateSubject(tag: String): WindowManagerStateSubject? =
+        doGetWmStateSubject(tag)
+
+    protected open fun doGetWmStateSubject(tag: String): WindowManagerStateSubject? {
+        return when (tag) {
+            Tag.START -> wmTraceSubject?.subjects?.firstOrNull()
+            Tag.END -> wmTraceSubject?.subjects?.lastOrNull()
+            else -> {
+                val trace = resultReader.readWmState(tag) ?: return null
+                WindowManagerStateSubject(trace.entries.first())
+            }
+        }
+    }
+
+    /** Truth subject that corresponds to a [LayerTraceEntry] */
+    private fun getLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? =
+        doGetLayerTraceEntrySubject(tag)
+
+    protected open fun doGetLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? {
+        return when (tag) {
+            Tag.START -> layersTraceSubject?.subjects?.firstOrNull()
+            Tag.END -> layersTraceSubject?.subjects?.lastOrNull()
+            else -> {
+                val trace = resultReader.readLayersDump(tag) ?: return null
+                return LayersTraceSubject(trace).first()
+            }
+        }
+    }
+
+    /** Truth subject that corresponds to a list of [FocusEvent] */
+    val eventLogSubject: EventLogSubject?
+        get() = doGetEventLogSubject()
+
+    protected open fun doGetEventLogSubject(): EventLogSubject? {
+        val trace = resultReader.readEventLogTrace() ?: return null
+        return EventLogSubject(trace)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionResult.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionResult.kt
new file mode 100644
index 0000000..927469b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionResult.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.assertors
+
+/** Base class for a FaaS assertion */
+data class AssertionResult(
+    override val assertion: IFaasAssertion,
+    override val assertionError: Throwable?,
+) : IAssertionResult {
+    override val failed = (assertionError !== null)
+    override val passed = !failed
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionTemplate.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionTemplate.kt
new file mode 100644
index 0000000..4c6b312
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/AssertionTemplate.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.assertors
+
+import android.tools.common.flicker.AssertionInvocationGroup
+import android.tools.common.flicker.AssertionInvocationGroup.NON_BLOCKING
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Base class for a FaaS assertion */
+abstract class AssertionTemplate : IAssertionTemplate {
+    override val assertionName = "${this@AssertionTemplate::class.simpleName}"
+    private var stabilityGroup: AssertionInvocationGroup = NON_BLOCKING
+
+    override fun createAssertion(scenarioInstance: IScenarioInstance): IFaasAssertion {
+        return object : IFaasAssertion {
+            override val name = this@AssertionTemplate.assertionName
+
+            override val stabilityGroup
+                get() = this@AssertionTemplate.stabilityGroup
+
+            override fun evaluate(): AssertionResult {
+                val wmTraceSubject =
+                    scenarioInstance.reader.readWmTrace()?.let { WindowManagerTraceSubject(it) }
+                val layersTraceSubject =
+                    scenarioInstance.reader.readLayersTrace()?.let { LayersTraceSubject(it) }
+
+                var assertionError: Throwable? = null
+                try {
+                    if (wmTraceSubject !== null) {
+                        doEvaluate(scenarioInstance, wmTraceSubject)
+                    }
+                    if (layersTraceSubject !== null) {
+                        doEvaluate(scenarioInstance, layersTraceSubject)
+                    }
+                    if (wmTraceSubject !== null && layersTraceSubject !== null) {
+                        doEvaluate(scenarioInstance, wmTraceSubject, layersTraceSubject)
+                    }
+                } catch (e: Throwable) {
+                    assertionError = e
+                }
+
+                return AssertionResult(this, assertionError)
+            }
+        }
+    }
+
+    /**
+     * Evaluates assertions that require only WM traces. NOTE: Will not run if WM trace is not
+     * available.
+     */
+    protected open fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        // Does nothing, unless overridden
+    }
+
+    /**
+     * Evaluates assertions that require only SF traces. NOTE: Will not run if layers trace is not
+     * available.
+     */
+    protected open fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        layerSubject: LayersTraceSubject
+    ) {
+        // Does nothing, unless overridden
+    }
+
+    /**
+     * Evaluates assertions that require both SF and WM traces. NOTE: Will not run if any of the
+     * traces are not available.
+     */
+    protected open fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject,
+        layerSubject: LayersTraceSubject
+    ) {
+        // Does nothing, unless overridden
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other == null) {
+            return false
+        }
+        // Ensure both assertions are instances of the same class.
+        return this::class == other::class
+    }
+
+    override fun hashCode(): Int {
+        var result = stabilityGroup.hashCode()
+        result = 31 * result + assertionName.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/ComponentTemplate.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/ComponentTemplate.kt
new file mode 100644
index 0000000..da2bd94
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/ComponentTemplate.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors
+
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.flicker.IScenarioInstance
+
+data class ComponentTemplate(
+    val name: String,
+    val build: (scenarioInstance: IScenarioInstance) -> IComponentMatcher
+) {
+    override fun equals(other: Any?): Boolean {
+        return other is ComponentTemplate && name == other.name && build == other.build
+    }
+
+    override fun hashCode(): Int {
+        return name.hashCode() * 39 + build.hashCode()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/Components.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/Components.kt
new file mode 100644
index 0000000..fca5b35
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/Components.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.assertors
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.FullComponentIdMatcher
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.traces.wm.Transition
+
+object Components {
+    val NAV_BAR = ComponentTemplate("Navbar") { ComponentNameMatcher.NAV_BAR }
+    val STATUS_BAR = ComponentTemplate("StatusBar") { ComponentNameMatcher.STATUS_BAR }
+    val LAUNCHER = ComponentTemplate("Launcher") { ComponentNameMatcher.LAUNCHER }
+
+    val OPENING_APP =
+        ComponentTemplate("OPENING_APP") { scenarioInstance: IScenarioInstance ->
+            openingAppFrom(
+                scenarioInstance.associatedTransition ?: error("Missing associated transition")
+            )
+        }
+    val CLOSING_APP =
+        ComponentTemplate("CLOSING_APP") { scenarioInstance: IScenarioInstance ->
+            closingAppFrom(
+                scenarioInstance.associatedTransition ?: error("Missing associated transition")
+            )
+        }
+
+    val EMPTY = ComponentTemplate("") { ComponentNameMatcher("", "") }
+
+    // TODO: Extract out common code between two functions below
+    private fun openingAppFrom(transition: Transition): IComponentMatcher {
+        val targetChanges =
+            transition.changes.filter {
+                it.transitMode == Transition.Companion.Type.OPEN ||
+                    it.transitMode == Transition.Companion.Type.TO_FRONT
+            }
+
+        val openingLayerIds = targetChanges.map { it.layerId }
+        require(openingLayerIds.size == 1) {
+            "Expected 1 opening layer but got ${openingLayerIds.size}"
+        }
+
+        val openingWindowIds = targetChanges.map { it.windowId }
+        require(openingWindowIds.size == 1) {
+            "Expected 1 opening window but got ${openingWindowIds.size}"
+        }
+
+        val windowId = openingWindowIds.first()
+        val layerId = openingLayerIds.first()
+        return FullComponentIdMatcher(windowId, layerId)
+    }
+
+    private fun closingAppFrom(transition: Transition): IComponentMatcher {
+        val targetChanges =
+            transition.changes.filter {
+                it.transitMode == Transition.Companion.Type.CLOSE ||
+                    it.transitMode == Transition.Companion.Type.TO_BACK
+            }
+
+        val closingLayerIds = targetChanges.map { it.layerId }
+        require(closingLayerIds.size == 1) {
+            "Expected 1 closing layer but got ${closingLayerIds.size}"
+        }
+
+        val closingWindowIds = targetChanges.map { it.windowId }
+        require(closingWindowIds.size == 1) {
+            "Expected 1 closing window but got ${closingWindowIds.size}"
+        }
+
+        val windowId = closingWindowIds.first()
+        val layerId = closingLayerIds.first()
+        return FullComponentIdMatcher(windowId, layerId)
+    }
+
+    val byType: Map<String, ComponentTemplate> =
+        mapOf("OPENING_APP" to OPENING_APP, "CLOSING_APP" to CLOSING_APP)
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/FaasData.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/FaasData.kt
new file mode 100644
index 0000000..e82d3cf
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/FaasData.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.assertors
+
+import android.tools.common.flicker.ScenarioInstance
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+
+data class FaasData(
+    val scenarioInstance: ScenarioInstance,
+    val entireWmTrace: WindowManagerTrace,
+    val entireLayersTrace: LayersTrace
+) {
+    fun toFacts(): Collection<Fact> {
+        return mutableListOf(
+                Fact("Extracted from WM trace start", entireWmTrace.entries.first().timestamp),
+                Fact("Extracted from WM trace end", entireWmTrace.entries.first().timestamp),
+                Fact("Extracted from SF trace start", entireLayersTrace.entries.first().timestamp),
+                Fact("Extracted from SF trace end", entireLayersTrace.entries.first().timestamp),
+                Fact("Scenario description", scenarioInstance.description),
+                Fact("Scenario rotation", scenarioInstance.startRotation),
+                Fact("Scenario start", "${scenarioInstance.startTimestamp}"),
+                Fact("Scenario end", "${scenarioInstance.endTimestamp}")
+            )
+            .apply {
+                if (scenarioInstance.associatedTransition != null) {
+                    this.add(
+                        Fact(
+                            "Associated transition changes",
+                            scenarioInstance.associatedTransition.changes.joinToString(
+                                "\n  -",
+                                "\n  -"
+                            ) { "${it.transitMode} ${it.layerId}" }
+                        )
+                    )
+                }
+                if (scenarioInstance.associatedCuj !== null) {
+                    this.add(Fact("Associated CUJ", scenarioInstance.associatedCuj))
+                }
+            }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionResult.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionResult.kt
new file mode 100644
index 0000000..2a82bec
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionResult.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.assertors
+
+interface IAssertionResult {
+    val assertion: IFaasAssertion
+    val passed: Boolean
+    val failed: Boolean
+        get() = !passed
+    val assertionError: Throwable?
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionTemplate.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionTemplate.kt
new file mode 100644
index 0000000..82c9eaf
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/IAssertionTemplate.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.assertors
+
+import android.tools.common.flicker.IScenarioInstance
+
+interface IAssertionTemplate {
+    val assertionName: String
+    fun createAssertion(scenarioInstance: IScenarioInstance): IFaasAssertion
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/IFaasAssertion.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/IFaasAssertion.kt
new file mode 100644
index 0000000..915aafb
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/IFaasAssertion.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.assertors
+
+import android.tools.common.flicker.AssertionInvocationGroup
+
+interface IFaasAssertion {
+    val name: String
+    val stabilityGroup: AssertionInvocationGroup
+
+    fun evaluate(): IAssertionResult
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesInvisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesInvisible.kt
new file mode 100644
index 0000000..99c9d54
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesInvisible.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
+ * created and/or becomes visible during the transition.
+ */
+class AppLayerBecomesInvisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isVisible(component.build(scenarioInstance))
+            .then()
+            .isInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesVisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesVisible.kt
new file mode 100644
index 0000000..7467760
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerBecomesVisible.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
+ * created and/or becomes visible during the transition.
+ */
+class AppLayerBecomesVisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isInvisible(component.build(scenarioInstance))
+            .then()
+            .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+            .then()
+            .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+            .then()
+            .isVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt
new file mode 100644
index 0000000..6472c2f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+class AppLayerCoversFullScreenAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
+        val startDisplayBounds =
+            layersTrace.entries.last().physicalDisplayBounds
+                ?: error("Missing physical display bounds")
+
+        layerSubject
+            .last()
+            .visibleRegion(component.build(scenarioInstance))
+            .coversExactly(startDisplayBounds)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtStart.kt
new file mode 100644
index 0000000..2b9de3f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerCoversFullScreenAtStart.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+class AppLayerCoversFullScreenAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
+        val startDisplayBounds =
+            layersTrace.entries.first().physicalDisplayBounds
+                ?: error("Missing physical display bounds")
+
+        layerSubject
+            .first()
+            .visibleRegion(component.build(scenarioInstance))
+            .coversExactly(startDisplayBounds)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtEnd.kt
new file mode 100644
index 0000000..0d812ee
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtEnd.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is invisible at the end of the transition */
+class AppLayerIsInvisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.last().isInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtStart.kt
new file mode 100644
index 0000000..9a69c44
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsInvisibleAtStart.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is invisible at the start of the transition */
+class AppLayerIsInvisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.first().isInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAlways.kt
new file mode 100644
index 0000000..8ee49da
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAlways.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is visible throughout the animation */
+class AppLayerIsVisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtEnd.kt
new file mode 100644
index 0000000..508e42a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtEnd.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is visible at the end of the transition */
+class AppLayerIsVisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.last().isVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtStart.kt
new file mode 100644
index 0000000..d72db81
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerIsVisibleAtStart.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is visible at the start of the transition */
+class AppLayerIsVisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.first().isVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReduces.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReduces.kt
new file mode 100644
index 0000000..cd4e679
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReduces.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks that the visible region of [component] always reduces during the animation */
+class AppLayerReduces(component: ComponentTemplate) : AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val layerMatcher = component.build(scenarioInstance)
+        val layerList = layerSubject.layers { layerMatcher.layerMatchesAnyOf(it) && it.isVisible }
+        layerList.zipWithNext { previous, current ->
+            current.visibleRegion.coversAtMost(previous.visibleRegion.region)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt
new file mode 100644
index 0000000..5c87616
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the [component] layer remains inside the display bounds throughout the whole animation
+ */
+class AppLayerRemainInsideDisplayBounds(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .invoke("appLayerRemainInsideDisplayBounds") { entry ->
+                val displays = entry.entry.displays
+                if (displays.isEmpty()) {
+                    entry.fail("No displays found")
+                }
+                displays.forEach { display ->
+                    entry
+                        .visibleRegion(component.build(scenarioInstance))
+                        .coversAtMost(display.layerStackSpace)
+                }
+            }
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReplacesLauncher.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReplacesLauncher.kt
new file mode 100644
index 0000000..d64c81f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppLayerReplacesLauncher.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Asserts that:
+ * ```
+ *     [Components.LAUNCHER] is visible at the start of the trace
+ *     [Components.LAUNCHER] becomes invisible during the trace and (in the same entry)
+ *     [component] becomes visible
+ *     [component] remains visible until the end of the trace
+ * ```
+ */
+class AppLayerReplacesLauncher(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isVisible(ComponentNameMatcher.LAUNCHER)
+            .then()
+            .isVisible(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesInvisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesInvisible.kt
new file mode 100644
index 0000000..2d79b5e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesInvisible.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
+ * created and/or becomes visible during the transition.
+ */
+class AppWindowBecomesInvisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowVisible(component.build(scenarioInstance))
+            .then()
+            .isAppWindowInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesPinned.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesPinned.kt
new file mode 100644
index 0000000..ef65d4a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesPinned.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks that [component] window becomes pinned */
+class AppWindowBecomesPinned(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .invoke("appWindowIsNotPinned") { it.isNotPinned(component.build(scenarioInstance)) }
+            .then()
+            .invoke("appWindowIsPinned") { it.isPinned(component.build(scenarioInstance)) }
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesTopWindow.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesTopWindow.kt
new file mode 100644
index 0000000..35d428a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesTopWindow.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
+ * created and/or becomes visible during the transition.
+ */
+class AppWindowBecomesTopWindow(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        val testApp = component.build(scenarioInstance)
+        wmSubject
+            .isAppWindowNotOnTop(testApp)
+            .then()
+            .isAppWindowOnTop(
+                testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
+            )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesVisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesVisible.kt
new file mode 100644
index 0000000..7f88616
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowBecomesVisible.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
+ * created and/or becomes visible during the transition.
+ */
+class AppWindowBecomesVisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowInvisible(component.build(scenarioInstance))
+            .then()
+            .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+            .then()
+            .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+            .then()
+            .isAppWindowVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt
new file mode 100644
index 0000000..353a185
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowCoversFullScreenAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
+        val startDisplayBounds =
+            layersTrace.entries.last().physicalDisplayBounds
+                ?: error("Missing physical display bounds")
+
+        wmSubject
+            .last()
+            .visibleRegion(component.build(scenarioInstance))
+            .coversExactly(startDisplayBounds)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtStart.kt
new file mode 100644
index 0000000..0e20a5b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowCoversFullScreenAtStart.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowCoversFullScreenAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
+        val startDisplayBounds =
+            layersTrace.entries.first().physicalDisplayBounds
+                ?: error("Missing physical display bounds")
+
+        wmSubject
+            .first()
+            .visibleRegion(component.build(scenarioInstance))
+            .coversExactly(startDisplayBounds)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtEnd.kt
new file mode 100644
index 0000000..91fd914
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtEnd.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks if the [getWindowState] layer is invisible at the end of the transition */
+class AppWindowIsInvisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.last().isAppWindowInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtStart.kt
new file mode 100644
index 0000000..bd25849
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsInvisibleAtStart.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowIsInvisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.first().isAppWindowInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsTopWindowAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsTopWindowAtStart.kt
new file mode 100644
index 0000000..a52a4d9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsTopWindowAtStart.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowIsTopWindowAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.first().isAppWindowOnTop(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAlways.kt
new file mode 100644
index 0000000..9402a0c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAlways.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks that [component] window remains visible throughout the transition */
+class AppWindowIsVisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.isAppWindowVisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtEnd.kt
new file mode 100644
index 0000000..f7f65bd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtEnd.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowIsVisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.last().isAppWindowVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtStart.kt
new file mode 100644
index 0000000..2d432a3
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowIsVisibleAtStart.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowIsVisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.first().isAppWindowVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtEnd.kt
new file mode 100644
index 0000000..b572e3b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtEnd.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowOnTopAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.last().isAppWindowOnTop(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtStart.kt
new file mode 100644
index 0000000..3a7fe4e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowOnTopAtStart.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+class AppWindowOnTopAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.first().isAppWindowOnTop(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt
new file mode 100644
index 0000000..3b6fd22
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that [component] window remains inside the display bounds throughout the whole animation
+ */
+class AppWindowRemainInsideDisplayBounds(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .invoke("appWindowRemainInsideDisplayBounds") { entry ->
+                val displays = entry.wmState.displays
+                if (displays.isEmpty()) {
+                    entry.fail("No displays found")
+                }
+                val display = entry.wmState.displays.sortedBy { it.id }.first()
+                entry
+                    .visibleRegion(component.build(scenarioInstance))
+                    .coversAtMost(display.displayRect)
+            }
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt
new file mode 100644
index 0000000..adcd80a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.assertors.Components
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that [Components.LAUNCHER] is the top visible app window at the start of the transition
+ * and that it is replaced by [component] during the transition
+ */
+class AppWindowReplacesLauncherAsTopWindow(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+            .then()
+            .isAppWindowOnTop(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AssertionTemplateWithComponent.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AssertionTemplateWithComponent.kt
new file mode 100644
index 0000000..3efc59e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/AssertionTemplateWithComponent.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.assertors.ComponentTemplate
+
+/** Base class for tests that require a [component] named window name */
+abstract class AssertionTemplateWithComponent(val component: ComponentTemplate) :
+    AssertionTemplate() {
+
+    override val assertionName = "${this::class.simpleName}(${component.name})"
+
+    override fun equals(other: Any?): Boolean {
+        if (other !is AssertionTemplateWithComponent) {
+            return false
+        }
+
+        // Check both assertions are instances of the same class.
+        if (this::class != component::class) {
+            return false
+        }
+
+        // TODO: Make sure equality is properly defined on the component
+        return other.component == component
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + component.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAlways.kt
new file mode 100644
index 0000000..d01ef84
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAlways.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer, during the whole
+ * transitions
+ */
+class EntireScreenCoveredAlways : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .invoke("entireScreenCovered") { entry ->
+                val displays = entry.entry.displays
+                if (displays.isEmpty()) {
+                    entry.fail("No displays found")
+                }
+                displays.forEach { display ->
+                    entry.visibleRegion().coversAtLeast(display.layerStackSpace)
+                }
+            }
+            .forAllEntries()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return other is EntireScreenCoveredAlways
+    }
+
+    override fun hashCode(): Int {
+        return this::class.hashCode()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtEnd.kt
new file mode 100644
index 0000000..136c69e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtEnd.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer, at the end of
+ * the transition
+ */
+class EntireScreenCoveredAtEnd : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val subject = layerSubject.last()
+        val displays = subject.entry.displays
+        if (displays.isEmpty()) {
+            subject.fail("No displays found")
+        }
+        displays.forEach { display ->
+            subject.visibleRegion().coversAtLeast(display.layerStackSpace)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtStart.kt
new file mode 100644
index 0000000..0e2bdb4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/EntireScreenCoveredAtStart.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the stack space of all displays is fully covered by any visible layer, at the start of
+ * the transition
+ */
+class EntireScreenCoveredAtStart : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val subject = layerSubject.first()
+        val displays = subject.entry.displays
+        if (displays.isEmpty()) {
+            subject.fail("No displays found")
+        }
+        displays.forEach { display ->
+            subject.visibleRegion().coversAtLeast(display.layerStackSpace)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherReplacesAppLayer.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherReplacesAppLayer.kt
new file mode 100644
index 0000000..9704431
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherReplacesAppLayer.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Asserts that:
+ * ```
+ *     [component] is visible at the start of the trace
+ *     [component] becomes invisible during the trace and (in the same entry)
+ *     [Components.LAUNCHER] becomes visible
+ * ```
+ */
+class LauncherReplacesAppLayer(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isVisible(component.build(scenarioInstance))
+            .then()
+            .isVisible(ComponentNameMatcher.LAUNCHER)
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesOutOfTop.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesOutOfTop.kt
new file mode 100644
index 0000000..aecebe6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesOutOfTop.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.assertors.Components
+
+/** Checks that [Components.LAUNCHER] starts on top and moves out of top during the transition */
+class LauncherWindowMovesOutOfTop : WindowMovesOutOfTop(Components.LAUNCHER)
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesToTop.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesToTop.kt
new file mode 100644
index 0000000..574d010
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowMovesToTop.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.assertors.Components
+
+/** Checks that [Components.LAUNCHER] starts not on top and moves to top during the transition */
+class LauncherWindowMovesToTop : WindowMovesToTop(Components.LAUNCHER)
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt
new file mode 100644
index 0000000..f785b52
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.assertors.Components
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that [component] is the top visible app window at the start of the transition and that it
+ * is replaced by [Components.LAUNCHER] during the transition
+ */
+class LauncherWindowReplacesAppAsTopWindow(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowOnTop(component.build(scenarioInstance))
+            .then()
+            .isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesInvisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesInvisible.kt
new file mode 100644
index 0000000..40eb900
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesInvisible.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the [componentMatcher] layer is visible at the start of the transition and becomes
+ * invisible
+ */
+class LayerBecomesInvisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isVisible(component.build(scenarioInstance))
+            .then()
+            .isInvisible(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesVisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesVisible.kt
new file mode 100644
index 0000000..6712cad
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerBecomesVisible.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the [componentMatcher] layer is invisible at the start of the transition and becomes
+ * visible
+ */
+class LayerBecomesVisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isInvisible(component.build(scenarioInstance))
+            .then()
+            .isVisible(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAlways.kt
new file mode 100644
index 0000000..9979a90
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAlways.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [componentMatcher] layer is invisible during the entire transition */
+class LayerIsInvisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtEnd.kt
new file mode 100644
index 0000000..55aab05
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtEnd.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [componentMatcher] layer is invisible at the end of the transition */
+class LayerIsInvisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.last().isInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtStart.kt
new file mode 100644
index 0000000..1fa1792
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsInvisibleAtStart.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [componentMatcher] layer is invisible at the start of the transition */
+class LayerIsInvisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.first().isInvisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAlways.kt
new file mode 100644
index 0000000..717fd00
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAlways.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [componentMatcher] layer is visible during the entire transition */
+class LayerIsVisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtEnd.kt
new file mode 100644
index 0000000..a5cec29
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtEnd.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is visible at the end of the transition */
+class LayerIsVisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.last().isVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtStart.kt
new file mode 100644
index 0000000..0a43fbd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/LayerIsVisibleAtStart.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/** Checks if the [component] layer is visible at the start of the transition */
+class LayerIsVisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.first().isVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesInvisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesInvisible.kt
new file mode 100644
index 0000000..c3b66d7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesInvisible.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that non-app window [component] is visible at the start of the transition and becomes
+ * invisible
+ */
+open class NonAppWindowBecomesInvisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isNonAppWindowVisible(component.build(scenarioInstance))
+            .then()
+            .isNonAppWindowInvisible(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesVisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesVisible.kt
new file mode 100644
index 0000000..c7355ff
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowBecomesVisible.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that non-app window [component] is invisible at the start of the transition and becomes
+ * visible
+ */
+class NonAppWindowBecomesVisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isNonAppWindowInvisible(component.build(scenarioInstance))
+            .then()
+            .isAppWindowVisible(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsInvisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsInvisibleAlways.kt
new file mode 100644
index 0000000..2cffd43
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsInvisibleAlways.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks if the [component] window is invisible during the entire transition */
+class NonAppWindowIsInvisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.isNonAppWindowInvisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAlways.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAlways.kt
new file mode 100644
index 0000000..19d6b7a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAlways.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks if the [component] window is visible during the entire transition */
+class NonAppWindowIsVisibleAlways(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.isNonAppWindowVisible(component.build(scenarioInstance)).forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt
new file mode 100644
index 0000000..f5c1dd9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks if the [component] window is visible at the end of the transition */
+class NonAppWindowIsVisibleAtEnd(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.last().isNonAppWindowVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtStart.kt
new file mode 100644
index 0000000..04f8a05
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/NonAppWindowIsVisibleAtStart.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks if the [component] window is visible at the end of the transition */
+class NonAppWindowIsVisibleAtStart(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.first().isNonAppWindowVisible(component.build(scenarioInstance))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/PipWindowBecomesInvisible.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/PipWindowBecomesInvisible.kt
new file mode 100644
index 0000000..aa4ae9c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/PipWindowBecomesInvisible.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that [component] window is pinned and visible at the start and then becomes unpinned and
+ * invisible at the same moment, and remains unpinned and invisible until the end of the transition
+ */
+class PipWindowBecomesInvisible(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        val appComponent = component
+        wmSubject
+            .invoke("hasPipWindow") {
+                it.isPinned(appComponent.build(scenarioInstance))
+                    .isAppWindowVisible(appComponent.build(scenarioInstance))
+            }
+            .then()
+            .invoke("!hasPipWindow") {
+                it.isNotPinned(appComponent.build(scenarioInstance))
+                    .isAppWindowInvisible(appComponent.build(scenarioInstance))
+            }
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/RotationLayerAppearsAndVanishes.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/RotationLayerAppearsAndVanishes.kt
new file mode 100644
index 0000000..cc541a9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/RotationLayerAppearsAndVanishes.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
+ * flicker, and disappears before the transition is complete.
+ */
+class RotationLayerAppearsAndVanishes(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject
+            .isVisible(component.build(scenarioInstance))
+            .then()
+            .isVisible(ComponentNameMatcher.ROTATION)
+            .then()
+            .isVisible(component.build(scenarioInstance))
+            .isInvisible(ComponentNameMatcher.ROTATION)
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtEnd.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtEnd.kt
new file mode 100644
index 0000000..87810b9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtEnd.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.PlatformConsts
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the [ComponentNameMatcher.STATUS_BAR] layer is placed at the correct position at the
+ * end of the transition
+ */
+class StatusBarLayerPositionAtEnd : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val subject = layerSubject.last()
+        subject
+            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+            .coversExactly(getExpectedStatusbarPosition(scenarioInstance))
+    }
+
+    // TODO: Maybe find another way to get the expected position that doesn't rely on use the data
+    // from the WM trace
+    // can we maybe dump another trace that just has system info for this purpose?
+    private fun getExpectedStatusbarPosition(scenarioInstance: IScenarioInstance): Region {
+        val wmState =
+            scenarioInstance.reader.readWmTrace()?.entries?.last()
+                ?: error("Missing wm trace entries")
+        val display =
+            wmState.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+        TODO("return WindowUtils.getExpectedStatusBarPosition(display)")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtStart.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtStart.kt
new file mode 100644
index 0000000..4cc7b56
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/StatusBarLayerPositionAtStart.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.PlatformConsts
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks if the [ComponentNameMatcher.STATUS_BAR] layer is placed at the correct position at the
+ * start of the transition
+ */
+class StatusBarLayerPositionAtStart : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        val subject = layerSubject.first()
+        subject
+            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+            .coversExactly(getExpectedStatusbarPosition(scenarioInstance))
+    }
+
+    // TODO: Maybe find another way to get the expected position that doesn't rely on use the data
+    // from the WM trace
+    // can we maybe dump another trace that just has system info for this purpose?
+    // TODO: Also this is duplicated code we can probably extract this out
+    private fun getExpectedStatusbarPosition(scenarioInstance: IScenarioInstance): Region {
+        val wmState =
+            scenarioInstance.reader.readWmTrace()?.entries?.last()
+                ?: error("Missing wm trace entries")
+        val display =
+            wmState.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+        TODO("return WindowUtils.getExpectedStatusBarPosition(display)")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
new file mode 100644
index 0000000..00875e1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+
+/**
+ * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+class VisibleLayersShownMoreThanOneConsecutiveEntry : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
+        layerSubject.visibleLayersShownMoreThanOneConsecutiveEntry().forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
new file mode 100644
index 0000000..0ebc280
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.AssertionTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/**
+ * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+class VisibleWindowsShownMoreThanOneConsecutiveEntry : AssertionTemplate() {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject.visibleWindowsShownMoreThanOneConsecutiveEntry().forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesOutOfTop.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesOutOfTop.kt
new file mode 100644
index 0000000..db5c279
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesOutOfTop.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks that [component] starts on top and moves out of top during the transition */
+open class WindowMovesOutOfTop(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowOnTop(component.build(scenarioInstance))
+            .then()
+            .isAppWindowNotOnTop(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesToTop.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesToTop.kt
new file mode 100644
index 0000000..ca2568b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/assertions/WindowMovesToTop.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.assertors.assertions
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.ComponentTemplate
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+
+/** Checks that [component] starts not on top and moves to top during the transition */
+open class WindowMovesToTop(component: ComponentTemplate) :
+    AssertionTemplateWithComponent(component) {
+    /** {@inheritDoc} */
+    override fun doEvaluate(
+        scenarioInstance: IScenarioInstance,
+        wmSubject: WindowManagerTraceSubject
+    ) {
+        wmSubject
+            .isAppWindowNotOnTop(component.build(scenarioInstance))
+            .then()
+            .isAppWindowOnTop(component.build(scenarioInstance))
+            .forAllEntries()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/factories/AssertionFactory.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/AssertionFactory.kt
new file mode 100644
index 0000000..f688b15
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/AssertionFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.assertors.factories
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.IFaasAssertion
+import android.tools.common.flicker.config.FlickerServiceConfig
+
+open class AssertionFactory : IAssertionFactory {
+    override fun generateAssertionsFor(
+        scenarioInstance: IScenarioInstance
+    ): Collection<IFaasAssertion> {
+        val assertionTemplates =
+            FlickerServiceConfig.getScenarioConfigFor(scenarioInstance.type).assertionTemplates
+        return assertionTemplates.map { it.createAssertion(scenarioInstance) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/factories/CombinedAssertionFactory.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/CombinedAssertionFactory.kt
new file mode 100644
index 0000000..0d94d22
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/CombinedAssertionFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.assertors.factories
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.IFaasAssertion
+
+class CombinedAssertionFactory(private val factories: List<IAssertionFactory>) : IAssertionFactory {
+    override fun generateAssertionsFor(
+        scenarioInstance: IScenarioInstance
+    ): Collection<IFaasAssertion> {
+        return CrossPlatform.log.withTracing("CombinedAssertionFactory#generateAssertionsFor") {
+            factories.flatMap { it.generateAssertionsFor(scenarioInstance) }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/factories/GeneratedAssertionsFactory.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/GeneratedAssertionsFactory.kt
new file mode 100644
index 0000000..215f3b1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/GeneratedAssertionsFactory.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.assertors.factories
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.IFaasAssertion
+
+class GeneratedAssertionsFactory : IAssertionFactory {
+    override fun generateAssertionsFor(
+        scenarioInstance: IScenarioInstance
+    ): Collection<IFaasAssertion> {
+        // TODO: Implement
+        return emptyList()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/factories/IAssertionFactory.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/IAssertionFactory.kt
new file mode 100644
index 0000000..a4f2ee9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/factories/IAssertionFactory.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.assertors.factories
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.flicker.assertors.IFaasAssertion
+
+interface IAssertionFactory {
+    // What format should the returned assertion be? Probably want to have data about the stability
+    // of the assertion here for the AssertionRunner to then decide how to run them based on the
+    // config? Or do we want it to be prefiltered by the AssertionFactories?
+    fun generateAssertionsFor(scenarioInstance: IScenarioInstance): Collection<IFaasAssertion>
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/runners/AssertionRunner.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/runners/AssertionRunner.kt
new file mode 100644
index 0000000..4380b9e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/runners/AssertionRunner.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.assertors.runners
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.flicker.assertors.IFaasAssertion
+
+class AssertionRunner : IAssertionRunner {
+    override fun execute(assertions: List<IFaasAssertion>): List<IAssertionResult> {
+        return CrossPlatform.log.withTracing("AssertionRunner#execute") {
+            assertions.map { it.evaluate() }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/assertors/runners/IAssertionRunner.kt b/libraries/flicker/src/android/tools/common/flicker/assertors/runners/IAssertionRunner.kt
new file mode 100644
index 0000000..1d8c920
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/assertors/runners/IAssertionRunner.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.assertors.runners
+
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.flicker.assertors.IFaasAssertion
+
+interface IAssertionRunner {
+    fun execute(assertions: List<IFaasAssertion>): List<IAssertionResult>
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/config/AssertionTemplates.kt b/libraries/flicker/src/android/tools/common/flicker/config/AssertionTemplates.kt
new file mode 100644
index 0000000..3997685
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/config/AssertionTemplates.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.config
+
+import android.tools.common.flicker.assertors.Components
+import android.tools.common.flicker.assertors.assertions.AppLayerBecomesInvisible
+import android.tools.common.flicker.assertors.assertions.AppLayerBecomesVisible
+import android.tools.common.flicker.assertors.assertions.AppLayerCoversFullScreenAtEnd
+import android.tools.common.flicker.assertors.assertions.AppLayerCoversFullScreenAtStart
+import android.tools.common.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
+import android.tools.common.flicker.assertors.assertions.AppLayerIsInvisibleAtStart
+import android.tools.common.flicker.assertors.assertions.AppLayerIsVisibleAtEnd
+import android.tools.common.flicker.assertors.assertions.AppLayerIsVisibleAtStart
+import android.tools.common.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.common.flicker.assertors.assertions.AppWindowBecomesTopWindow
+import android.tools.common.flicker.assertors.assertions.AppWindowBecomesVisible
+import android.tools.common.flicker.assertors.assertions.AppWindowCoversFullScreenAtEnd
+import android.tools.common.flicker.assertors.assertions.AppWindowCoversFullScreenAtStart
+import android.tools.common.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
+import android.tools.common.flicker.assertors.assertions.AppWindowIsInvisibleAtStart
+import android.tools.common.flicker.assertors.assertions.AppWindowIsTopWindowAtStart
+import android.tools.common.flicker.assertors.assertions.AppWindowIsVisibleAtEnd
+import android.tools.common.flicker.assertors.assertions.AppWindowIsVisibleAtStart
+import android.tools.common.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.common.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.common.flicker.assertors.assertions.EntireScreenCoveredAlways
+import android.tools.common.flicker.assertors.assertions.EntireScreenCoveredAtEnd
+import android.tools.common.flicker.assertors.assertions.EntireScreenCoveredAtStart
+import android.tools.common.flicker.assertors.assertions.LayerIsVisibleAlways
+import android.tools.common.flicker.assertors.assertions.LayerIsVisibleAtEnd
+import android.tools.common.flicker.assertors.assertions.LayerIsVisibleAtStart
+import android.tools.common.flicker.assertors.assertions.NonAppWindowIsVisibleAlways
+import android.tools.common.flicker.assertors.assertions.VisibleLayersShownMoreThanOneConsecutiveEntry
+import android.tools.common.flicker.assertors.assertions.VisibleWindowsShownMoreThanOneConsecutiveEntry
+
+object AssertionTemplates {
+    val COMMON_ASSERTIONS =
+        listOf(
+            EntireScreenCoveredAtStart(),
+            EntireScreenCoveredAtEnd(),
+            EntireScreenCoveredAlways(),
+            VisibleWindowsShownMoreThanOneConsecutiveEntry(),
+            VisibleLayersShownMoreThanOneConsecutiveEntry(),
+        )
+
+    val NAV_BAR_ASSERTIONS =
+        listOf(
+            LayerIsVisibleAtStart(Components.NAV_BAR),
+            LayerIsVisibleAtEnd(Components.NAV_BAR),
+            NonAppWindowIsVisibleAlways(Components.NAV_BAR),
+        )
+
+    val STATUS_BAR_ASSERTIONS =
+        listOf(
+            NonAppWindowIsVisibleAlways(Components.STATUS_BAR),
+            LayerIsVisibleAlways(Components.STATUS_BAR),
+        )
+
+    val APP_LAUNCH_ASSERTIONS =
+        COMMON_ASSERTIONS +
+            listOf(
+                AppLayerIsInvisibleAtStart(Components.OPENING_APP),
+                AppLayerIsVisibleAtEnd(Components.OPENING_APP),
+                AppLayerBecomesVisible(Components.OPENING_APP),
+                AppWindowBecomesVisible(Components.OPENING_APP),
+                AppWindowBecomesTopWindow(Components.OPENING_APP),
+            )
+
+    val APP_CLOSE_ASSERTIONS =
+        COMMON_ASSERTIONS +
+            listOf(
+                AppLayerIsVisibleAtStart(Components.CLOSING_APP),
+                AppLayerIsInvisibleAtEnd(Components.CLOSING_APP),
+                AppWindowIsVisibleAtStart(Components.CLOSING_APP),
+                AppWindowIsInvisibleAtEnd(Components.CLOSING_APP),
+                AppLayerBecomesInvisible(Components.CLOSING_APP),
+                AppWindowBecomesInvisible(Components.CLOSING_APP),
+                AppWindowIsTopWindowAtStart(Components.CLOSING_APP),
+            )
+
+    val APP_LAUNCH_FROM_HOME_ASSERTIONS =
+        APP_LAUNCH_ASSERTIONS +
+            listOf(
+                AppLayerIsVisibleAtStart(Components.LAUNCHER),
+                AppLayerIsInvisibleAtEnd(Components.LAUNCHER),
+            )
+
+    val APP_CLOSE_TO_HOME_ASSERTIONS =
+        APP_CLOSE_ASSERTIONS +
+            listOf(
+                AppLayerIsInvisibleAtStart(Components.LAUNCHER),
+                AppLayerIsVisibleAtEnd(Components.LAUNCHER),
+                AppWindowIsInvisibleAtStart(Components.LAUNCHER),
+                AppWindowIsVisibleAtEnd(Components.LAUNCHER),
+                AppWindowBecomesTopWindow(Components.LAUNCHER),
+            )
+
+    val APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS =
+        COMMON_ASSERTIONS +
+            APP_LAUNCH_ASSERTIONS +
+            listOf(
+                // None specific to opening from notification yet
+                )
+
+    val LAUNCHER_QUICK_SWITCH_ASSERTIONS =
+        COMMON_ASSERTIONS +
+            APP_LAUNCH_ASSERTIONS +
+            APP_CLOSE_ASSERTIONS +
+            listOf(
+                AppWindowCoversFullScreenAtStart(Components.CLOSING_APP),
+                AppLayerCoversFullScreenAtStart(Components.CLOSING_APP),
+                AppWindowCoversFullScreenAtEnd(Components.OPENING_APP),
+                AppLayerCoversFullScreenAtEnd(Components.OPENING_APP),
+                AppWindowOnTopAtStart(Components.CLOSING_APP),
+                AppWindowOnTopAtEnd(Components.OPENING_APP),
+                AppWindowBecomesInvisible(Components.CLOSING_APP),
+                AppLayerBecomesInvisible(Components.CLOSING_APP),
+                AppWindowBecomesVisible(Components.OPENING_APP),
+                AppLayerBecomesVisible(Components.OPENING_APP),
+            )
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/config/FaasScenarioType.kt b/libraries/flicker/src/android/tools/common/flicker/config/FaasScenarioType.kt
new file mode 100644
index 0000000..d1e206d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/config/FaasScenarioType.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.config
+
+enum class FaasScenarioType {
+    COMMON,
+    LAUNCHER_APP_LAUNCH_FROM_ICON,
+    APP_CLOSE_TO_HOME,
+    LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+    NOTIFICATION_APP_START,
+    LAUNCHER_QUICK_SWITCH
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/config/FlickerServiceConfig.kt b/libraries/flicker/src/android/tools/common/flicker/config/FlickerServiceConfig.kt
new file mode 100644
index 0000000..ffbaaf0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/config/FlickerServiceConfig.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.config
+
+import android.tools.common.flicker.assertors.IAssertionTemplate
+import android.tools.common.flicker.config.AssertionTemplates.APP_CLOSE_TO_HOME_ASSERTIONS
+import android.tools.common.flicker.config.AssertionTemplates.APP_LAUNCH_FROM_HOME_ASSERTIONS
+import android.tools.common.flicker.config.AssertionTemplates.APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS
+import android.tools.common.flicker.config.AssertionTemplates.COMMON_ASSERTIONS
+import android.tools.common.flicker.config.AssertionTemplates.LAUNCHER_QUICK_SWITCH_ASSERTIONS
+import android.tools.common.flicker.config.TransitionFilters.CLOSE_APP_TO_LAUNCHER_FILTER
+import android.tools.common.flicker.config.TransitionFilters.OPEN_APP_TRANSITION_FILTER
+import android.tools.common.flicker.config.TransitionFilters.QUICK_SWITCH_TRANSITION_FILTER
+import android.tools.common.flicker.config.TransitionFilters.QUICK_SWITCH_TRANSITION_MERGE
+import android.tools.common.flicker.extractors.EntireTraceExtractor
+import android.tools.common.flicker.extractors.IScenarioExtractor
+import android.tools.common.flicker.extractors.TaggedScenarioExtractor
+import android.tools.common.flicker.extractors.TransitionMatcher
+import android.tools.common.traces.events.CujType
+
+object FlickerServiceConfig {
+    /** EDIT THIS CONFIG TO ADD SCENARIOS TO FAAS */
+    fun getScenarioConfigFor(type: FaasScenarioType): ScenarioConfig =
+        when (type) {
+            FaasScenarioType.COMMON ->
+                ScenarioConfig(
+                    extractor = EntireTraceExtractor(FaasScenarioType.COMMON),
+                    assertionTemplates = COMMON_ASSERTIONS
+                )
+            FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_ICON ->
+                ScenarioConfig(
+                    extractor =
+                        TaggedScenarioExtractor(
+                            targetTag = CujType.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+                            type,
+                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
+                        ),
+                    assertionTemplates = APP_LAUNCH_FROM_HOME_ASSERTIONS
+                )
+            FaasScenarioType.APP_CLOSE_TO_HOME ->
+                ScenarioConfig(
+                    extractor =
+                        TaggedScenarioExtractor(
+                            targetTag = CujType.CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+                            type,
+                            transitionMatcher = TransitionMatcher(CLOSE_APP_TO_LAUNCHER_FILTER)
+                        ),
+                    assertionTemplates = APP_CLOSE_TO_HOME_ASSERTIONS
+                )
+            FaasScenarioType.NOTIFICATION_APP_START ->
+                ScenarioConfig(
+                    extractor =
+                        TaggedScenarioExtractor(
+                            targetTag = CujType.CUJ_NOTIFICATION_APP_START,
+                            type,
+                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
+                        ),
+                    assertionTemplates = APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS
+                )
+            FaasScenarioType.LAUNCHER_QUICK_SWITCH ->
+                ScenarioConfig(
+                    extractor =
+                        TaggedScenarioExtractor(
+                            targetTag = CujType.CUJ_LAUNCHER_QUICK_SWITCH,
+                            type,
+                            transitionMatcher =
+                                TransitionMatcher(
+                                    QUICK_SWITCH_TRANSITION_FILTER,
+                                    finalTransform = QUICK_SWITCH_TRANSITION_MERGE
+                                )
+                        ),
+                    assertionTemplates = LAUNCHER_QUICK_SWITCH_ASSERTIONS
+                )
+            FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_RECENTS ->
+                ScenarioConfig(
+                    extractor =
+                        TaggedScenarioExtractor(
+                            targetTag = CujType.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+                            type,
+                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
+                        ),
+                    assertionTemplates = APP_LAUNCH_FROM_HOME_ASSERTIONS
+                )
+        }
+
+    fun getExtractors(): List<IScenarioExtractor> {
+        return FaasScenarioType.values().map { getScenarioConfigFor(it).extractor }
+    }
+}
+
+data class ScenarioConfig(
+    val extractor: IScenarioExtractor,
+    val assertionTemplates: Collection<IAssertionTemplate>
+)
diff --git a/libraries/flicker/src/android/tools/common/flicker/config/TransitionFilters.kt b/libraries/flicker/src/android/tools/common/flicker/config/TransitionFilters.kt
new file mode 100644
index 0000000..4148311
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/config/TransitionFilters.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.config
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.extractors.TransitionsTransform
+import android.tools.common.traces.wm.Transition
+
+object TransitionFilters {
+    val OPEN_APP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
+        ts.filter { t ->
+            t.changes.any {
+                it.transitMode == Transition.Companion.Type.OPEN || // cold launch
+                it.transitMode == Transition.Companion.Type.TO_FRONT // warm launch
+            }
+        }
+    }
+
+    val CLOSE_APP_TO_LAUNCHER_FILTER: TransitionsTransform = { ts, _, reader ->
+        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
+        val layers =
+            layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
+        val launcherLayers = layers.filter { ComponentNameMatcher.LAUNCHER.layerMatchesAnyOf(it) }
+
+        ts.filter { t ->
+            t.changes.any {
+                it.transitMode == Transition.Companion.Type.CLOSE ||
+                    it.transitMode == Transition.Companion.Type.TO_BACK
+            } &&
+                t.changes.any { change ->
+                    launcherLayers.any { it.id == change.layerId }
+                    change.transitMode == Transition.Companion.Type.TO_FRONT
+                }
+        }
+    }
+
+    val QUICK_SWITCH_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
+        ts.filter { t ->
+            t.changes.size == 2 &&
+                t.changes.any { it.transitMode == Transition.Companion.Type.TO_BACK } &&
+                t.changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT }
+        }
+    }
+
+    val QUICK_SWITCH_TRANSITION_MERGE: TransitionsTransform = { transitions, _, _ ->
+        require(transitions.size == 2) { "Expected 2 transitions but got ${transitions.size}" }
+
+        require(transitions[0].changes.size == 2)
+        require(transitions[0].changes.any { it.transitMode == Transition.Companion.Type.TO_BACK })
+        require(transitions[0].changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT })
+
+        require(transitions[1].changes.size == 2)
+        require(transitions[1].changes.any { it.transitMode == Transition.Companion.Type.TO_BACK })
+        require(transitions[1].changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT })
+
+        val candidateWallpaper1 =
+            transitions[0].changes.first { it.transitMode == Transition.Companion.Type.TO_FRONT }
+        val candidateWallpaper2 =
+            transitions[1].changes.first { it.transitMode == Transition.Companion.Type.TO_BACK }
+
+        require(candidateWallpaper1.layerId == candidateWallpaper2.layerId)
+
+        val closingAppChange =
+            transitions[0].changes.first { it.transitMode == Transition.Companion.Type.TO_BACK }
+        val openingAppChange =
+            transitions[1].changes.first { it.transitMode == Transition.Companion.Type.TO_FRONT }
+
+        listOf(
+            Transition(
+                start = transitions[0].start,
+                sendTime = transitions[0].sendTime,
+                startTransactionId = transitions[0].startTransactionId,
+                // NOTE: Relies on the implementation detail that the second
+                // finishTransaction is merged into the first and applied.
+                finishTransactionId = transitions[0].finishTransactionId,
+                changes = listOf(closingAppChange, openingAppChange),
+                played = true,
+                aborted = false
+            )
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/CombinedScenarioExtractor.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/CombinedScenarioExtractor.kt
new file mode 100644
index 0000000..6cb4b4a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/CombinedScenarioExtractor.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.extractors
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.io.IReader
+
+class CombinedScenarioExtractor(private val extractors: List<IScenarioExtractor>) :
+    IScenarioExtractor {
+    override fun extract(reader: IReader): List<IScenarioInstance> {
+        return CrossPlatform.log.withTracing("CombinedScenarioExtractor#extract") {
+            extractors.flatMap { it.extract(reader) }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/EntireTraceExtractor.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/EntireTraceExtractor.kt
new file mode 100644
index 0000000..2a1a816
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/EntireTraceExtractor.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.extractors
+
+import android.tools.common.flicker.ScenarioInstance
+import android.tools.common.flicker.config.FaasScenarioType
+import android.tools.common.io.IReader
+
+class EntireTraceExtractor(val type: FaasScenarioType) : IScenarioExtractor {
+    override fun extract(reader: IReader): List<ScenarioInstance> {
+        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
+
+        return listOf(
+            ScenarioInstance(
+                type,
+                startRotation =
+                    layersTrace.entries.first().physicalDisplay?.transform?.getRotation()
+                        ?: error("Missing display"),
+                endRotation = layersTrace.entries.last().physicalDisplay?.transform?.getRotation()
+                        ?: error("Missing display"),
+                startTimestamp = layersTrace.entries.first().timestamp,
+                endTimestamp = layersTrace.entries.last().timestamp,
+                associatedCuj = null,
+                associatedTransition = null,
+                reader = reader
+            )
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/IScenarioExtractor.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/IScenarioExtractor.kt
new file mode 100644
index 0000000..8ebadb5
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/IScenarioExtractor.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.extractors
+
+import android.tools.common.flicker.IScenarioInstance
+import android.tools.common.io.IReader
+
+interface IScenarioExtractor {
+    fun extract(reader: IReader): List<IScenarioInstance>
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/ITransitionMatcher.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/ITransitionMatcher.kt
new file mode 100644
index 0000000..126d1f0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/ITransitionMatcher.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.extractors
+
+import android.tools.common.io.IReader
+import android.tools.common.traces.events.Cuj
+import android.tools.common.traces.wm.Transition
+
+interface ITransitionMatcher {
+    fun getTransition(cujEntry: Cuj, reader: IReader): Transition?
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/TaggedScenarioExtractor.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/TaggedScenarioExtractor.kt
new file mode 100644
index 0000000..3416404
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/TaggedScenarioExtractor.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.extractors
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.ScenarioInstance
+import android.tools.common.flicker.config.FaasScenarioType
+import android.tools.common.io.IReader
+import android.tools.common.traces.events.CujType
+import kotlin.math.max
+
+class TaggedScenarioExtractor(
+    val targetTag: CujType,
+    val type: FaasScenarioType,
+    val transitionMatcher: ITransitionMatcher,
+    val associatedTransitionRequired: Boolean = true
+) : IScenarioExtractor {
+    override fun extract(reader: IReader): List<ScenarioInstance> {
+
+        val wmTrace = reader.readWmTrace()
+        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
+        val cujTrace = reader.readCujTrace() ?: error("Missing CUJ trace")
+
+        val targetCujEntries =
+            cujTrace.entries.filter { it.cuj === targetTag }.filter { !it.canceled }
+
+        if (targetCujEntries.isEmpty()) {
+            // No scenarios to extract here
+            return emptyList()
+        }
+
+        return targetCujEntries.map { cujEntry ->
+            val associatedTransition = transitionMatcher.getTransition(cujEntry, reader)
+
+            require(
+                cujEntry.startTimestamp.hasAllTimestamps && cujEntry.endTimestamp.hasAllTimestamps
+            )
+
+            // There is a delay between when we flag that transition as finished with the CUJ tags
+            // and when it is actually finished on the SF side. We try and account for that by
+            // checking when the finish transaction is actually applied.
+            // TODO: Figure out how to get the vSyncId that the Jank tracker actually gets to avoid
+            //       relying on the transition and have a common end point.
+            val finishTransactionAppliedTimestamp =
+                if (associatedTransition != null) {
+                    val transactionsTrace =
+                        reader.readTransactionsTrace() ?: error("Missing transactions trace")
+                    val finishTransaction =
+                        transactionsTrace.allTransactions.firstOrNull {
+                            it.id == associatedTransition.finishTransactionId
+                        }
+                            ?: error("Finish transaction not found")
+                    require(
+                        layersTrace.entries.first().vSyncId <= finishTransaction.appliedVSyncId &&
+                            finishTransaction.appliedVSyncId <= layersTrace.entries.last().vSyncId
+                    ) { "Finish transaction not in layer trace" }
+                    val appliedInLayerEntry =
+                        layersTrace.entries.first { it.vSyncId >= finishTransaction.appliedVSyncId }
+                    appliedInLayerEntry.timestamp
+                } else {
+                    CrossPlatform.timestamp.min()
+                }
+
+            val wmEntryAtTransitionFinished =
+                wmTrace?.entries?.first { it.timestamp >= finishTransactionAppliedTimestamp }
+
+            val startTimestamp = cujEntry.startTimestamp
+            val endTimestamp =
+                CrossPlatform.timestamp.from(
+                    elapsedNanos =
+                        max(
+                            cujEntry.endTimestamp.elapsedNanos,
+                            wmEntryAtTransitionFinished?.timestamp?.elapsedNanos ?: -1L
+                        ),
+                    systemUptimeNanos =
+                        max(
+                            cujEntry.endTimestamp.systemUptimeNanos,
+                            finishTransactionAppliedTimestamp.systemUptimeNanos
+                        ),
+                    unixNanos =
+                        max(
+                            cujEntry.endTimestamp.unixNanos,
+                            finishTransactionAppliedTimestamp.unixNanos
+                        )
+                )
+
+            ScenarioInstance(
+                type,
+                startRotation =
+                    layersTrace
+                        .getEntryAt(startTimestamp)
+                        .displays
+                        .first { !it.isVirtual && it.layerStackSpace.isNotEmpty }
+                        .transform
+                        .getRotation(),
+                endRotation =
+                    layersTrace
+                        .getEntryAt(endTimestamp)
+                        .displays
+                        .first { !it.isVirtual && it.layerStackSpace.isNotEmpty }
+                        .transform
+                        .getRotation(),
+                startTimestamp = startTimestamp,
+                endTimestamp = endTimestamp,
+                associatedCuj = cujEntry.cuj,
+                associatedTransition = associatedTransition,
+                reader = reader.slice(startTimestamp, endTimestamp)
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/extractors/TransitionMatcher.kt b/libraries/flicker/src/android/tools/common/flicker/extractors/TransitionMatcher.kt
new file mode 100644
index 0000000..e7f74d4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/extractors/TransitionMatcher.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.extractors
+
+import android.tools.common.MILLISECOND_AS_NANOSECONDS
+import android.tools.common.flicker.extractors.TransitionTransforms.inCujRangeFilter
+import android.tools.common.flicker.extractors.TransitionTransforms.mergeTrampolineTransitions
+import android.tools.common.flicker.extractors.TransitionTransforms.noOpTransitionsTransform
+import android.tools.common.io.IReader
+import android.tools.common.traces.events.Cuj
+import android.tools.common.traces.wm.Transition
+
+typealias TransitionsTransform =
+    (transitions: List<Transition>, cujEntry: Cuj, reader: IReader) -> List<Transition>
+
+class TransitionMatcher(
+    private val mainTransform: TransitionsTransform,
+    private val finalTransform: TransitionsTransform = noOpTransitionsTransform,
+    private val associatedTransitionRequired: Boolean = true,
+    // Transformations applied, in order, to all transitions the reader returns to end up with the
+    // targeted transition.
+    private val transforms: List<TransitionsTransform> =
+        listOf(mainTransform, inCujRangeFilter, mergeTrampolineTransitions, finalTransform)
+) : ITransitionMatcher {
+    override fun getTransition(cujEntry: Cuj, reader: IReader): Transition? {
+        val transitionsTrace = reader.readTransitionsTrace() ?: error("Missing transitions trace")
+
+        val completeTransitions = transitionsTrace.entries.filter { !it.isIncomplete }
+
+        val matchedTransitions =
+            transforms.fold(completeTransitions) { transitions, transform ->
+                transform(transitions, cujEntry, reader)
+            }
+
+        require(!associatedTransitionRequired || matchedTransitions.isNotEmpty()) {
+            "Required an associated transition for " +
+                "${cujEntry.cuj.name}(${cujEntry.startTimestamp},${cujEntry.endTimestamp}) " +
+                "but no transition left after all filters from: " +
+                "[\n${transitionsTrace.entries.joinToString(",\n").prependIndent()}\n]!"
+        }
+
+        require(!associatedTransitionRequired || matchedTransitions.size == 1) {
+            "Got too many associated transitions expected only 1."
+        }
+
+        return if (matchedTransitions.isNotEmpty()) matchedTransitions[0] else null
+    }
+}
+
+object TransitionTransforms {
+    val inCujRangeFilter: TransitionsTransform = { transitions, cujEntry, _ ->
+        transitions.filter { transition ->
+            val transitionSentWithinCujTags =
+                cujEntry.startTimestamp <= transition.sendTime &&
+                    transition.sendTime <= cujEntry.endTimestamp
+
+            // TODO: This threshold should be made more robust. Can fail to match on slower devices.
+            val toleranceNanos = 50 * MILLISECOND_AS_NANOSECONDS
+            val transitionSentJustBeforeCujStart =
+                cujEntry.startTimestamp - toleranceNanos <= transition.sendTime &&
+                    transition.sendTime <= cujEntry.startTimestamp
+
+            return@filter transitionSentWithinCujTags || transitionSentJustBeforeCujStart
+        }
+    }
+
+    val mergeTrampolineTransitions: TransitionsTransform = { transitions, _, reader ->
+        require(transitions.size <= 2)
+        if (
+            transitions.size == 2 &&
+                isTrampolinedOpenTransition(transitions[0], transitions[1], reader)
+        ) {
+            // Remove the trampoline transition
+            listOf(transitions[0])
+        } else {
+            transitions
+        }
+    }
+
+    val noOpTransitionsTransform: TransitionsTransform = { transitions, _, _ -> transitions }
+
+    private fun isTrampolinedOpenTransition(
+        firstTransition: Transition,
+        secondTransition: Transition,
+        reader: IReader
+    ): Boolean {
+        val candidateTaskLayers =
+            firstTransition.changes
+                .filter {
+                    it.transitMode == Transition.Companion.Type.OPEN ||
+                        it.transitMode == Transition.Companion.Type.TO_FRONT
+                }
+                .map { it.layerId }
+        if (candidateTaskLayers.isEmpty()) {
+            return false
+        }
+
+        require(candidateTaskLayers.size == 1) {
+            "Unhandled case (more than 1 task candidate) in isTrampolinedOpenTransition()"
+        }
+
+        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
+        val layers =
+            layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
+
+        val candidateTaskLayerId = candidateTaskLayers[0]
+        val candidateTaskLayer = layers.first { it.id == candidateTaskLayerId }
+        if (!candidateTaskLayer.name.contains("Task")) {
+            return false
+        }
+
+        val candidateTrampolinedActivities =
+            secondTransition.changes
+                .filter { it.transitMode == Transition.Companion.Type.CLOSE }
+                .map { it.layerId }
+        val candidateTargetActivities =
+            secondTransition.changes
+                .filter {
+                    it.transitMode == Transition.Companion.Type.OPEN ||
+                        it.transitMode == Transition.Companion.Type.TO_FRONT
+                }
+                .map { it.layerId }
+        if (candidateTrampolinedActivities.isEmpty() || candidateTargetActivities.isEmpty()) {
+            return false
+        }
+
+        require(candidateTargetActivities.size == 1) {
+            "Unhandled case (more than 1 trampolined candidate) in " +
+                "isTrampolinedOpenTransition()"
+        }
+        require(candidateTargetActivities.size == 1) {
+            "Unhandled case (more than 1 target candidate) in isTrampolinedOpenTransition()"
+        }
+
+        val candidateTrampolinedActivityId = candidateTargetActivities[0]
+        val candidateTrampolinedActivity = layers.first { it.id == candidateTrampolinedActivityId }
+        if (candidateTrampolinedActivity.parent?.id != candidateTaskLayerId) {
+            return false
+        }
+
+        val candidateTargetActivityId = candidateTargetActivities[0]
+        val candidateTargetActivity = layers.first { it.id == candidateTargetActivityId }
+        if (candidateTargetActivity.parent?.id != candidateTaskLayerId) {
+            return false
+        }
+
+        return true
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubject.kt
new file mode 100644
index 0000000..a2b7f3b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubject.kt
@@ -0,0 +1,106 @@
+/*
+ * 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
+
+import android.tools.common.flicker.assertions.Fact
+
+/** Subject for flicker checks */
+data class CheckSubject<T>(
+    private val actualValue: T?,
+    private val subject: FlickerSubject,
+    private val lazyMessage: () -> String,
+) {
+    fun isEqual(expectedValue: T?) {
+        if (actualValue != expectedValue) {
+            failWithFactForExpectedValue(Fact("expected to be equal to", expectedValue))
+        }
+    }
+
+    fun isNotEqual(expectedValue: T?) {
+        if (actualValue == expectedValue) {
+            failWithFactForExpectedValue(Fact("expected to be different from", expectedValue))
+        }
+    }
+
+    fun isNull() {
+        if (actualValue != null) {
+            failWithFactForExpectedValue(Fact("expected to be", null))
+        }
+    }
+
+    fun isNotNull() {
+        if (actualValue == null) {
+            failWithFactForExpectedValue(Fact("expected not to be", null))
+        }
+    }
+
+    fun isLower(expectedValue: T?) {
+        if (
+            actualValue == null ||
+                expectedValue == null ||
+                (actualValue as Comparable<T>) >= expectedValue
+        ) {
+            failWithFactForExpectedValue(Fact("expected to be lower than", expectedValue))
+        }
+    }
+
+    fun isLowerOrEqual(expectedValue: T?) {
+        if (
+            actualValue == null ||
+                expectedValue == null ||
+                (actualValue as Comparable<T>) > expectedValue
+        ) {
+            failWithFactForExpectedValue(Fact("expected to be lower or equal to", expectedValue))
+        }
+    }
+
+    fun isGreater(expectedValue: T) {
+        if (
+            actualValue == null ||
+                expectedValue == null ||
+                (actualValue as Comparable<T>) <= expectedValue
+        ) {
+            failWithFactForExpectedValue(Fact("expected to be greater than", expectedValue))
+        }
+    }
+
+    fun isGreaterOrEqual(expectedValue: T) {
+        if (
+            actualValue == null ||
+                expectedValue == null ||
+                (actualValue as Comparable<T>) < expectedValue
+        ) {
+            failWithFactForExpectedValue(Fact("expected to be greater or equal to", expectedValue))
+        }
+    }
+
+    fun <U> contains(expectedValue: U) {
+        if (actualValue !is List<*> || !(actualValue as List<U>).contains(expectedValue)) {
+            failWithFactForExpectedValue(Fact("expected to contain", expectedValue))
+        }
+    }
+
+    private fun failWithFactForExpectedValue(factForExpectedValue: Fact) {
+        val facts =
+            listOf(
+                Fact("Assertion failed", lazyMessage()),
+                Fact("Actual value", actualValue),
+                factForExpectedValue,
+            )
+        subject.fail(facts)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubjectBuilder.kt b/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubjectBuilder.kt
new file mode 100644
index 0000000..74de004
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/CheckSubjectBuilder.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+/** Subject builder for flicker checks */
+data class CheckSubjectBuilder(
+    private val subject: FlickerSubject,
+    private val lazyMessage: () -> String
+) {
+    fun <T> that(actual: T?): CheckSubject<T> {
+        return CheckSubject(actual, subject, lazyMessage)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/FlickerAssertionError.kt b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerAssertionError.kt
new file mode 100644
index 0000000..72d34c9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerAssertionError.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+import kotlin.AssertionError
+
+/** Exception type for assertion errors caused by flicker subjects */
+class FlickerAssertionError(message: String, cause: Throwable?) : AssertionError(message, cause)
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubject.kt
new file mode 100644
index 0000000..79a9c6e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubject.kt
@@ -0,0 +1,95 @@
+/*
+ * 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
+
+import android.tools.common.Timestamp
+import android.tools.common.flicker.assertions.Fact
+
+/** Base subject for flicker assertions */
+abstract class FlickerSubject {
+    abstract val timestamp: Timestamp
+    protected abstract val parent: FlickerSubject?
+
+    protected abstract val selfFacts: List<Fact>
+    val completeFacts: List<Fact>
+        get() {
+            val facts = selfFacts.toMutableList()
+            parent?.run {
+                val ancestorFacts = this.completeFacts
+                facts.addAll(ancestorFacts)
+            }
+            return facts
+        }
+
+    /**
+     * Fails an assertion on a subject
+     *
+     * @param reason for the failure
+     */
+    open fun fail(reason: List<Fact>): FlickerSubject = apply {
+        require(reason.isNotEmpty()) { "Failure should contain at least 1 fact" }
+        throw FlickerSubjectException(timestamp, reason + completeFacts)
+    }
+
+    fun fail(reason: Fact, vararg rest: Fact): FlickerSubject = apply {
+        val what = mutableListOf(reason).also { it.addAll(rest) }
+        fail(what)
+    }
+
+    /**
+     * Fails an assertion on a subject
+     *
+     * @param reason for the failure
+     */
+    fun fail(reason: Fact): FlickerSubject = apply { fail(listOf(reason)) }
+
+    /**
+     * Fails an assertion on a subject
+     *
+     * @param reason for the failure
+     */
+    fun fail(reason: String): FlickerSubject = apply { fail(Fact("Reason", reason)) }
+
+    /**
+     * Fails an assertion on a subject
+     *
+     * @param reason for the failure
+     * @param value for the failure
+     */
+    fun fail(reason: String, value: Any): FlickerSubject = apply { fail(Fact(reason, value)) }
+
+    /**
+     * Fails an assertion on a subject
+     *
+     * @param reason for the failure
+     */
+    fun fail(reason: Throwable) {
+        if (reason is FlickerSubjectException) {
+            throw reason
+        } else {
+            throw FlickerSubjectException(timestamp, completeFacts, reason)
+        }
+    }
+
+    fun check(lazyMessage: () -> String): CheckSubjectBuilder {
+        return CheckSubjectBuilder(this, lazyMessage)
+    }
+
+    companion object {
+        const val ASSERTION_TAG = "Assertion"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubjectException.kt b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubjectException.kt
new file mode 100644
index 0000000..720aff2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerSubjectException.kt
@@ -0,0 +1,49 @@
+/*
+ * 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
+
+import android.tools.common.Timestamp
+import android.tools.common.flicker.assertions.Fact
+
+/** Exception thrown by flicker subjects */
+class FlickerSubjectException(
+    timestamp: Timestamp,
+    val facts: List<Fact>,
+    override val cause: Throwable? = null
+) : AssertionError() {
+    override val message = buildString {
+        appendLine(errorType)
+
+        appendLine()
+        appendLine("What? ")
+        cause?.message?.split("\n")?.forEach {
+            appendLine()
+            appendLine(it.prependIndent("\t"))
+        }
+
+        appendLine()
+        appendLine("Where?")
+        appendLine(timestamp.toString().prependIndent("\t"))
+
+        appendLine()
+        appendLine("Facts")
+        facts.forEach { appendLine(it.toString().prependIndent("\t")) }
+    }
+
+    private val errorType: String =
+        if (cause == null) "Flicker assertion error" else "Unknown error"
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/FlickerTraceSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerTraceSubject.kt
new file mode 100644
index 0000000..f68f2e0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/FlickerTraceSubject.kt
@@ -0,0 +1,163 @@
+/*
+ * 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
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.assertions.AssertionsChecker
+import android.tools.common.flicker.assertions.Fact
+
+/** Base subject for flicker trace assertions */
+abstract class FlickerTraceSubject<EntrySubject : FlickerSubject> : FlickerSubject() {
+    override val timestamp
+        get() = subjects.firstOrNull()?.timestamp ?: CrossPlatform.timestamp.empty()
+    override val selfFacts by lazy {
+        listOf(
+            Fact("Trace start", subjects.firstOrNull()?.timestamp),
+            Fact("Trace end", subjects.lastOrNull()?.timestamp)
+        )
+    }
+
+    protected val assertionsChecker = AssertionsChecker<EntrySubject>()
+    private var newAssertionBlock = true
+
+    abstract val subjects: List<EntrySubject>
+
+    fun hasAssertions() = !assertionsChecker.isEmpty()
+
+    /**
+     * Adds a new assertion block (if preceded by [then]) or appends an assertion to the latest
+     * existing assertion block
+     *
+     * @param name Assertion name
+     * @param isOptional If this assertion is optional or must pass
+     */
+    protected fun addAssertion(
+        name: String,
+        isOptional: Boolean = false,
+        assertion: (EntrySubject) -> Unit
+    ) {
+        if (newAssertionBlock) {
+            assertionsChecker.add(name, isOptional, assertion)
+        } else {
+            assertionsChecker.append(name, isOptional, assertion)
+        }
+        newAssertionBlock = false
+    }
+
+    /** Run the assertions for all trace entries */
+    fun forAllEntries() {
+        require(subjects.isNotEmpty()) { "Trace is empty" }
+        assertionsChecker.test(subjects)
+    }
+
+    /** User-defined entry point for the first trace entry */
+    fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty")
+
+    /** User-defined entry point for the last trace entry */
+    fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty")
+
+    /**
+     * Signal that the last assertion set is complete. The next assertion added will start a new set
+     * of assertions.
+     *
+     * E.g.: checkA().then().checkB()
+     *
+     * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
+     * after checkA passes.
+     */
+    open fun then(): FlickerTraceSubject<EntrySubject> = apply { startAssertionBlock() }
+
+    /**
+     * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
+     * end of the trace without passing any assertion, return a failure with the name/reason from
+     * the first assertion
+     *
+     * @return
+     */
+    open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> = apply {
+        assertionsChecker.skipUntilFirstAssertion()
+    }
+
+    /**
+     * Signal that the last assertion set is complete. The next assertion added will start a new set
+     * of assertions.
+     *
+     * E.g.: checkA().then().checkB()
+     *
+     * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
+     * after checkA passes.
+     */
+    private fun startAssertionBlock() {
+        newAssertionBlock = true
+    }
+
+    /**
+     * Checks whether all the trace entries on the list are visible for more than one consecutive
+     * entry
+     *
+     * Ignore the first and last trace subjects. This is necessary because WM and SF traces log
+     * entries only when a change occurs.
+     *
+     * If the trace starts immediately before an animation or if it stops immediately after one, the
+     * first and last entry may contain elements that are visible only for that entry. Those
+     * elements, however, are not flickers, since they existed on the screen before or after the
+     * test.
+     *
+     * @param [visibleEntriesProvider] a list of all the entries with their name and index
+     */
+    protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
+        visibleEntriesProvider: (EntrySubject) -> Set<String>
+    ) {
+        if (subjects.isEmpty()) {
+            return
+        }
+        // Duplicate the first and last trace subjects to prevent them from triggering failures
+        // since WM and SF traces log entries only when a change occurs
+        val firstState = subjects.first()
+        val lastState = subjects.last()
+        val subjects =
+            subjects.toMutableList().also {
+                it.add(lastState)
+                it.add(0, firstState)
+            }
+        var lastVisible = visibleEntriesProvider(subjects.first())
+        val lastNew = lastVisible.toMutableSet()
+
+        // first subject was already taken
+        subjects.drop(1).forEachIndexed { index, entrySubject ->
+            val currentVisible = visibleEntriesProvider(entrySubject)
+            val newVisible = currentVisible.filter { it !in lastVisible }
+            lastNew.removeAll(currentVisible)
+
+            if (lastNew.isNotEmpty()) {
+                val prevEntry = subjects[index]
+                prevEntry.fail("$lastNew is not visible for 2 entries")
+            }
+            lastNew.addAll(newVisible)
+            lastVisible = currentVisible
+        }
+
+        if (lastNew.isNotEmpty()) {
+            val lastEntry = subjects.last()
+            lastEntry.fail("$lastNew is not visible for 2 entries")
+        }
+    }
+
+    override fun toString(): String =
+        "${this::class.simpleName}" +
+            "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/events/EventLogSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/events/EventLogSubject.kt
new file mode 100644
index 0000000..14fef0b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/events/EventLogSubject.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.events
+
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.events.FocusEvent
+
+/** Truth subject for [FocusEvent] objects. */
+class EventLogSubject(val eventLog: EventLog) : FlickerSubject() {
+    override val timestamp = CrossPlatform.timestamp.empty()
+    override val parent = null
+    override val selfFacts by lazy {
+        listOf(
+            Fact("Trace start", "${eventLog.entries.firstOrNull()?.timestamp}"),
+            Fact("Trace end", "${eventLog.entries.lastOrNull()?.timestamp}")
+        )
+    }
+
+    private val subjects by lazy { eventLog.focusEvents.map { FocusEventSubject(it, this) } }
+
+    private val _focusChanges by lazy {
+        val focusList = mutableListOf<String>()
+        eventLog.focusEvents.firstOrNull { !it.hasFocus() }?.let { focusList.add(it.window) }
+        focusList + eventLog.focusEvents.filter { it.hasFocus() }.map { it.window }
+    }
+
+    fun focusChanges(vararg windows: String) = apply {
+        if (windows.isNotEmpty()) {
+            val focusChanges =
+                _focusChanges.dropWhile { !it.contains(windows.first()) }.take(windows.size)
+            val success =
+                windows.size <= focusChanges.size &&
+                    focusChanges.zip(windows).all { (focus, search) -> focus.contains(search) }
+
+            if (!success) {
+                fail(
+                    Fact("Expected", windows.joinToString(",")),
+                    Fact("Found", focusChanges.joinToString(","))
+                )
+            }
+        }
+    }
+
+    fun focusDoesNotChange() = apply { check(_focusChanges.isEmpty()) { "Focus does not change" } }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/events/FocusEventSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/events/FocusEventSubject.kt
new file mode 100644
index 0000000..734168a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/events/FocusEventSubject.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.events
+
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.traces.events.FocusEvent
+
+class FocusEventSubject(val event: FocusEvent, override val parent: EventLogSubject?) :
+    FlickerSubject() {
+    override val timestamp = event.timestamp
+    override val selfFacts by lazy { listOf(Fact(event.toString())) }
+
+    fun hasFocus() {
+        check { "Has focus" }.that(event.hasFocus()).isEqual(true)
+    }
+
+    fun hasNotFocus() {
+        check { "Has not focus" }.that(event.hasFocus()).isEqual(false)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/layers/ILayerSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/layers/ILayerSubject.kt
new file mode 100644
index 0000000..ba9ea24
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/layers/ILayerSubject.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.layers
+
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Layer
+
+/** Base interface for Layer trace and state assertions */
+interface ILayerSubject<LayerSubjectType, RegionSubjectType> {
+    /** Asserts that the current SurfaceFlinger state doesn't contain layers */
+    fun isEmpty(): LayerSubjectType
+
+    /** Asserts that the current SurfaceFlinger state contains layers */
+    fun isNotEmpty(): LayerSubjectType
+
+    /**
+     * Obtains the region occupied by all layers matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
+     * Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise, calculates the
+     * visible region when the information is not available from the CE
+     */
+    fun visibleRegion(
+        componentMatcher: IComponentMatcher? = null,
+        useCompositionEngineRegionOnly: Boolean = true
+    ): RegionSubjectType
+
+    /**
+     * Asserts the state contains a [Layer] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun contains(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts the state doesn't contain a [Layer] matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    fun notContains(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts that a [Layer] matching [componentMatcher] is visible.
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isVisible(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts that a [Layer] matching [componentMatcher] doesn't exist or is invisible.
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isInvisible(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts that the entry contains a visible splash screen [Layer] for a [layer] matching
+     * [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isSplashScreenVisibleFor(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts that a [Layer] matching [componentMatcher] has a color set on it.
+     *
+     * @param componentMatcher Components to search
+     */
+    fun hasColor(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Asserts that all [Layer]s matching [componentMatcher] have a no color set on them.
+     *
+     * @param componentMatcher Components to search
+     */
+    fun hasNoColor(componentMatcher: IComponentMatcher): LayerSubjectType
+
+    /**
+     * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name] containing
+     * [name] in [frameNumber].
+     *
+     * Always returns a subject, event when the layer doesn't exist. To verify if layer actually
+     * exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
+     *
+     * @return LayerSubject that can be used to make assertions on a single layer matching [name]
+     * and [frameNumber].
+     */
+    fun layer(name: String, frameNumber: Long): LayerSubject?
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerSubject.kt
new file mode 100644
index 0000000..5e679aa
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerSubject.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.layers
+
+import android.tools.common.Timestamp
+import android.tools.common.datatypes.Size
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.surfaceflinger.Layer
+
+/**
+ * Subject for [Layer] objects, used to make assertions over behaviors that occur on a single layer
+ * of a SurfaceFlinger state.
+ *
+ * To make assertions over a layer from a state it is recommended to create a subject using
+ * [LayerTraceEntrySubject.layer](layerName)
+ *
+ * Alternatively, it is also possible to use [LayerSubject](myLayer).
+ *
+ * Example:
+ * ```
+ *    val trace = LayersTraceParser().parse(myTraceFile)
+ *    val subject = LayersTraceSubject(trace).first()
+ *        .layer("ValidLayer")
+ *        .exists()
+ *        .hasBufferSize(BUFFER_SIZE)
+ *        .invoke { myCustomAssertion(this) }
+ * ```
+ */
+class LayerSubject(
+    public override val parent: FlickerSubject,
+    override val timestamp: Timestamp,
+    val layer: Layer
+) : FlickerSubject() {
+    val isVisible: Boolean
+        get() = layer.isVisible
+    val isInvisible: Boolean
+        get() = !layer.isVisible
+    val name: String
+        get() = layer.name
+
+    /** Visible region calculated by the Composition Engine */
+    val visibleRegion: RegionSubject
+        get() = RegionSubject(layer.visibleRegion, this, timestamp)
+
+    val visibilityReason: Array<String>
+        get() = layer.visibilityReason
+
+    /**
+     * Visible region calculated by the Composition Engine (when available) or calculated based on
+     * the layer bounds and transform
+     */
+    val screenBounds: RegionSubject
+        get() = RegionSubject(layer.screenBounds, this, timestamp)
+
+    override val selfFacts = listOf(Fact("Frame", layer.currFrame), Fact("Layer", layer.name))
+
+    /** If the [layer] exists, executes a custom [assertion] on the current subject */
+    operator fun invoke(assertion: (Layer) -> Unit): LayerSubject = apply { assertion(this.layer) }
+
+    /**
+     * Asserts that current subject has an [Layer.activeBuffer]
+     *
+     * @param expected expected buffer size
+     */
+    fun hasBufferSize(expected: Size): LayerSubject = apply {
+        val bufferSize = Size.from(layer.activeBuffer.width, layer.activeBuffer.height)
+        check { "Buffer size" }.that(bufferSize).isEqual(expected)
+    }
+
+    /**
+     * Asserts that current subject has an [Layer.screenBounds]
+     *
+     * @param size expected layer bounds size
+     */
+    fun hasLayerSize(size: Size): LayerSubject = apply {
+        val layerSize =
+            Size.from(layer.screenBounds.width.toInt(), layer.screenBounds.height.toInt())
+        check { "Number of layers" }.that(layerSize).isEqual(size)
+    }
+
+    /**
+     * Asserts that current subject has an [Layer.effectiveScalingMode] equals to
+     * [expectedScalingMode]
+     */
+    fun hasScalingMode(expectedScalingMode: Int): LayerSubject = apply {
+        val actualScalingMode = layer.effectiveScalingMode
+        check(actualScalingMode == expectedScalingMode) {
+            "Scaling mode. Actual: $actualScalingMode, expected: $expectedScalingMode"
+        }
+    }
+
+    /**
+     * Asserts that current subject has an [Layer.bufferTransform] orientation equals to
+     * [expectedOrientation]
+     */
+    fun hasBufferOrientation(expectedOrientation: Int): LayerSubject = apply {
+        // see Transform::getOrientation
+        val bufferTransformType = layer.bufferTransform.type ?: 0
+        val actualOrientation = (bufferTransformType shr 8) and 0xFF
+        check(actualOrientation == expectedOrientation) {
+            "Buffer orientation. Actual: $actualOrientation, expected: $expectedOrientation"
+        }
+    }
+
+    override fun toString(): String {
+        return "Layer:${layer.name} frame#${layer.currFrame}"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerTraceEntrySubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerTraceEntrySubject.kt
new file mode 100644
index 0000000..e3922ca
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayerTraceEntrySubject.kt
@@ -0,0 +1,305 @@
+/*
+ * 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.layers
+
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+
+/**
+ * Subject for [LayerTraceEntry] objects, used to make assertions over behaviors that occur on a
+ * single SurfaceFlinger state.
+ *
+ * To make assertions over a specific state from a trace it is recommended to create a subject using
+ * [LayersTraceSubject](myTrace) and select the specific state using:
+ * ```
+ *     [LayersTraceSubject.first]
+ *     [LayersTraceSubject.last]
+ *     [LayersTraceSubject.entry]
+ * ```
+ * Alternatively, it is also possible to use [LayerTraceEntrySubject](myState).
+ *
+ * Example:
+ * ```
+ *    val trace = LayersTraceParser().parse(myTraceFile)
+ *    val subject = LayersTraceSubject(trace).first()
+ *        .contains("ValidLayer")
+ *        .notContains("ImaginaryLayer")
+ *        .coversExactly(DISPLAY_AREA)
+ *        .invoke { myCustomAssertion(this) }
+ * ```
+ */
+class LayerTraceEntrySubject(
+    val entry: LayerTraceEntry,
+    val trace: LayersTrace? = null,
+    override val parent: FlickerSubject? = null
+) : FlickerSubject(), ILayerSubject<LayerTraceEntrySubject, RegionSubject> {
+    override val timestamp = entry.timestamp
+    override val selfFacts = listOf(Fact("SF State", entry))
+
+    val subjects by lazy { entry.flattenedLayers.map { LayerSubject(this, timestamp, it) } }
+
+    /** Executes a custom [assertion] on the current subject */
+    operator fun invoke(assertion: (LayerTraceEntry) -> Unit): LayerTraceEntrySubject = apply {
+        assertion(this.entry)
+    }
+
+    /** {@inheritDoc} */
+    override fun isEmpty(): LayerTraceEntrySubject = apply {
+        check(entry.flattenedLayers.isEmpty()) { "SF state is empty" }
+    }
+
+    /** {@inheritDoc} */
+    override fun isNotEmpty(): LayerTraceEntrySubject = apply {
+        check(entry.flattenedLayers.isNotEmpty()) { "SF state is not empty" }
+    }
+
+    /** See [visibleRegion] */
+    fun visibleRegion(): RegionSubject =
+        visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = true)
+
+    /** See [visibleRegion] */
+    fun visibleRegion(componentMatcher: IComponentMatcher): RegionSubject =
+        visibleRegion(componentMatcher, useCompositionEngineRegionOnly = true)
+
+    /** {@inheritDoc} */
+    override fun visibleRegion(
+        componentMatcher: IComponentMatcher?,
+        useCompositionEngineRegionOnly: Boolean
+    ): RegionSubject {
+        val selectedLayers =
+            if (componentMatcher == null) {
+                // No filters so use all subjects
+                subjects
+            } else {
+                subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
+            }
+
+        if (selectedLayers.isEmpty()) {
+            val str = componentMatcher?.toLayerIdentifier() ?: "<any>"
+            fail(
+                listOf(
+                    Fact(ASSERTION_TAG, "visibleRegion($str)"),
+                    Fact("Use composition engine region", useCompositionEngineRegionOnly),
+                    Fact("Could not find layers", str)
+                )
+            )
+        }
+
+        val visibleLayers = selectedLayers.filter { it.isVisible }
+        return if (useCompositionEngineRegionOnly) {
+            val visibleAreas = visibleLayers.mapNotNull { it.layer.visibleRegion }.toTypedArray()
+            RegionSubject(visibleAreas, this, timestamp)
+        } else {
+            val visibleAreas = visibleLayers.map { it.layer.screenBounds }.toTypedArray()
+            RegionSubject(visibleAreas, this, timestamp)
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun contains(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        val found = componentMatcher.layerMatchesAnyOf(entry.flattenedLayers)
+        if (!found) {
+            fail(
+                Fact(ASSERTION_TAG, "contains(${componentMatcher.toLayerIdentifier()})"),
+                Fact("Could not find", componentMatcher.toLayerIdentifier())
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun notContains(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        val layers = subjects.map { it.layer }
+        val notContainsComponent =
+            componentMatcher.check(layers) { matchedLayers -> matchedLayers.isEmpty() }
+
+        if (notContainsComponent) {
+            return@apply
+        }
+
+        val failedEntries = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
+        val failureFacts =
+            mutableListOf(
+                Fact(ASSERTION_TAG, "notContains(${componentMatcher.toLayerIdentifier()})")
+            )
+        failedEntries.forEach { entry -> failureFacts.add(Fact("Found", entry)) }
+        failedEntries.firstOrNull()?.fail(failureFacts)
+    }
+
+    /** {@inheritDoc} */
+    override fun isVisible(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        contains(componentMatcher)
+        val layers = subjects.map { it.layer }
+        val hasVisibleSubject =
+            componentMatcher.check(layers) { matchedLayers ->
+                matchedLayers.any { layer -> layer.isVisible }
+            }
+
+        if (hasVisibleSubject) {
+            return@apply
+        }
+
+        val matchingSubjects = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
+        val failedEntries = matchingSubjects.filter { it.isInvisible }
+        val failureFacts =
+            mutableListOf(Fact(ASSERTION_TAG, "isVisible(${componentMatcher.toLayerIdentifier()})"))
+
+        failedEntries.forEach { entry ->
+            failureFacts.add(Fact("Is Invisible", entry))
+            failureFacts.addAll(entry.visibilityReason.map { Fact("Invisibility reason", it) })
+        }
+        failedEntries.firstOrNull()?.fail(failureFacts)
+    }
+
+    /** {@inheritDoc} */
+    override fun isInvisible(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        val layers = subjects.map { it.layer }
+        val hasInvisibleComponent =
+            componentMatcher.check(layers) { componentLayers ->
+                componentLayers.all { layer ->
+                    subjects.first { subject -> subject.layer == layer }.isInvisible
+                }
+            }
+
+        if (hasInvisibleComponent) {
+            return@apply
+        }
+
+        val matchingSubjects = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
+        val failedEntries = matchingSubjects.filter { it.isVisible }
+        val failureFacts =
+            mutableListOf(
+                Fact(ASSERTION_TAG, "isInvisible(${componentMatcher.toLayerIdentifier()})")
+            )
+        failureFacts.addAll(failedEntries.map { Fact("Is Visible", it) })
+        failedEntries.firstOrNull()?.fail(failureFacts)
+    }
+
+    /** {@inheritDoc} */
+    override fun isSplashScreenVisibleFor(
+        componentMatcher: IComponentMatcher
+    ): LayerTraceEntrySubject = apply {
+        var target: FlickerSubject? = null
+        var reason: Fact? = null
+
+        val matchingLayer =
+            subjects.map { it.layer }.filter { componentMatcher.layerMatchesAnyOf(it) }
+        val matchingActivityRecords = matchingLayer.mapNotNull { getActivityRecordFor(it) }
+
+        if (matchingActivityRecords.isEmpty()) {
+            fail(
+                Fact(
+                    ASSERTION_TAG,
+                    "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})"
+                ),
+                Fact("Could not find Activity Record layer", componentMatcher.toLayerIdentifier())
+            )
+            return this
+        }
+
+        // Check the matched activity record layers for containing splash screens
+        for (layer in matchingActivityRecords) {
+            val splashScreenContainers = layer.children.filter { it.name.contains("Splash Screen") }
+            val splashScreenLayers =
+                splashScreenContainers.flatMap {
+                    it.children.filter { childLayer -> childLayer.name.contains("Splash Screen") }
+                }
+
+            if (splashScreenLayers.all { it.isHiddenByParent || !it.isVisible }) {
+                reason = Fact("No splash screen visible for", layer.name)
+                target = subjects.first { it.layer == layer }
+                continue
+            }
+            reason = null
+            target = null
+            break
+        }
+
+        reason?.run {
+            target?.fail(
+                Fact(
+                    ASSERTION_TAG,
+                    "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})"
+                ),
+                reason
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun hasColor(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        contains(componentMatcher)
+
+        val hasColorLayer =
+            componentMatcher.check(subjects.map { it.layer }) {
+                it.any { layer -> layer.color.isNotEmpty }
+            }
+
+        if (!hasColorLayer) {
+            fail(Fact(ASSERTION_TAG, "hasColor(${componentMatcher.toLayerIdentifier()})"))
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun hasNoColor(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
+        val hasNoColorLayer =
+            componentMatcher.check(subjects.map { it.layer }) {
+                it.all { layer -> layer.color.isEmpty }
+            }
+
+        if (!hasNoColorLayer) {
+            fail(Fact(ASSERTION_TAG, "hasNoColor(${componentMatcher.toLayerIdentifier()})"))
+        }
+    }
+
+    /** See [layer] */
+    fun layer(componentMatcher: IComponentMatcher): LayerSubject? {
+        return layer { componentMatcher.layerMatchesAnyOf(it) }
+    }
+
+    /** {@inheritDoc} */
+    override fun layer(name: String, frameNumber: Long): LayerSubject? {
+        return layer { it.name.contains(name) && it.currFrame == frameNumber }
+    }
+
+    /**
+     * Obtains a [LayerSubject] for the first occurrence of a [Layer] matching [predicate] or throws
+     * and error if the layer doesn't exist
+     *
+     * @param predicate to search for a layer
+     *
+     * @return [LayerSubject] that can be used to make assertions
+     */
+    fun layer(predicate: (Layer) -> Boolean): LayerSubject? =
+        subjects.firstOrNull { predicate(it.layer) }
+
+    private fun getActivityRecordFor(layer: Layer): Layer? {
+        if (layer.name.startsWith("ActivityRecord{")) {
+            return layer
+        }
+        val parent = layer.parent ?: return null
+        return getActivityRecordFor(parent)
+    }
+
+    override fun toString(): String {
+        return "LayerTraceEntrySubject($entry)"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayersTraceSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayersTraceSubject.kt
new file mode 100644
index 0000000..68d513b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/layers/LayersTraceSubject.kt
@@ -0,0 +1,295 @@
+/*
+ * 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.layers
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.EdgeExtensionComponentMatcher
+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.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.LayersTrace
+
+/**
+ * Subject for [LayersTrace] 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
+ * [LayersTraceSubject](myTrace).
+ *
+ * Example:
+ * ```
+ *    val trace = LayersTraceParser().parse(myTraceFile)
+ *    val subject = LayersTraceSubject(trace)
+ *        .contains("ValidLayer")
+ *        .notContains("ImaginaryLayer")
+ *        .coversExactly(DISPLAY_AREA)
+ *        .forAllEntries()
+ * ```
+ * Example2:
+ * ```
+ *    val trace = LayersTraceParser().parse(myTraceFile)
+ *    val subject = LayersTraceSubject(trace) {
+ *        check("Custom check") { myCustomAssertion(this) }
+ *    }
+ * ```
+ */
+class LayersTraceSubject(
+    val trace: LayersTrace,
+    override val parent: LayersTraceSubject? = null,
+    val facts: Collection<Fact> = emptyList()
+) :
+    FlickerTraceSubject<LayerTraceEntrySubject>(),
+    ILayerSubject<LayersTraceSubject, RegionTraceSubject> {
+
+    override val selfFacts by lazy {
+        val allFacts = super.selfFacts.toMutableList()
+        allFacts.addAll(facts)
+        allFacts
+    }
+
+    override val subjects by lazy { trace.entries.map { LayerTraceEntrySubject(it, trace, this) } }
+
+    /** {@inheritDoc} */
+    override fun then(): LayersTraceSubject = apply { super.then() }
+
+    /** {@inheritDoc} */
+    override fun isEmpty(): LayersTraceSubject = apply {
+        check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isNotEmpty(): LayersTraceSubject = apply {
+        check { "Trace is not empty" }.that(trace.entries.isNotEmpty()).isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun layer(name: String, frameNumber: Long): LayerSubject {
+        val value = subjects.firstNotNullOfOrNull { it.layer(name, frameNumber) }
+        if (value == null) {
+            fail("Layer does not exist $name")
+        }
+        requireNotNull(value)
+        return value
+    }
+
+    /** @return List of [LayerSubject]s matching [name] in the order they appear on the trace */
+    fun layers(name: String): List<LayerSubject> =
+        subjects.mapNotNull { it.layer { layer -> layer.name.contains(name) } }
+
+    /**
+     * @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
+     */
+    fun layers(predicate: (Layer) -> Boolean): List<LayerSubject> =
+        subjects.mapNotNull { it.layer { layer -> predicate(layer) } }
+
+    /** Checks that all visible layers are shown for more than one consecutive entry */
+    fun visibleLayersShownMoreThanOneConsecutiveEntry(
+        ignoreLayers: List<IComponentMatcher> =
+            listOf(
+                ComponentNameMatcher.SPLASH_SCREEN,
+                ComponentNameMatcher.SNAPSHOT,
+                ComponentNameMatcher.IME_SNAPSHOT,
+                EdgeExtensionComponentMatcher()
+            )
+    ): LayersTraceSubject = apply {
+        visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
+            subject.entry.visibleLayers
+                .filter {
+                    ignoreLayers.none { componentMatcher -> componentMatcher.layerMatchesAnyOf(it) }
+                }
+                .map { it.name }
+                .toSet()
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun notContains(componentMatcher: IComponentMatcher): LayersTraceSubject =
+        notContains(componentMatcher, isOptional = false)
+
+    /** See [notContains] */
+    fun notContains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
+        apply {
+            addAssertion("notContains(${componentMatcher.toLayerIdentifier()})", isOptional) {
+                it.notContains(componentMatcher)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun contains(componentMatcher: IComponentMatcher): LayersTraceSubject =
+        contains(componentMatcher, isOptional = false)
+
+    /** See [contains] */
+    fun contains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
+        apply {
+            addAssertion("contains(${componentMatcher.toLayerIdentifier()})", isOptional) {
+                it.contains(componentMatcher)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun isVisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
+        isVisible(componentMatcher, isOptional = false)
+
+    /** See [isVisible] */
+    fun isVisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
+        apply {
+            addAssertion("isVisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
+                it.isVisible(componentMatcher)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun isInvisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
+        isInvisible(componentMatcher, isOptional = false)
+
+    /** See [isInvisible] */
+    fun isInvisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
+        apply {
+            addAssertion("isInvisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
+                it.isInvisible(componentMatcher)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun isSplashScreenVisibleFor(componentMatcher: IComponentMatcher): LayersTraceSubject =
+        isSplashScreenVisibleFor(componentMatcher, isOptional = false)
+
+    /** {@inheritDoc} */
+    override fun hasColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
+        addAssertion("hasColor(${componentMatcher.toLayerIdentifier()})") {
+            it.hasColor(componentMatcher)
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun hasNoColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
+        addAssertion("hasNoColor(${componentMatcher.toLayerIdentifier()})") {
+            it.hasNoColor(componentMatcher)
+        }
+    }
+
+    /** See [isSplashScreenVisibleFor] */
+    fun isSplashScreenVisibleFor(
+        componentMatcher: IComponentMatcher,
+        isOptional: Boolean
+    ): LayersTraceSubject = apply {
+        addAssertion(
+            "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})",
+            isOptional
+        ) { it.isSplashScreenVisibleFor(componentMatcher) }
+    }
+
+    /** See [visibleRegion] */
+    fun visibleRegion(): RegionTraceSubject =
+        visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = false)
+
+    /** See [visibleRegion] */
+    fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject =
+        visibleRegion(componentMatcher, useCompositionEngineRegionOnly = false)
+
+    /** {@inheritDoc} */
+    override fun visibleRegion(
+        componentMatcher: IComponentMatcher?,
+        useCompositionEngineRegionOnly: Boolean
+    ): RegionTraceSubject {
+        val regionTrace =
+            RegionTrace(
+                componentMatcher,
+                subjects
+                    .map {
+                        it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly)
+                            .regionEntry
+                    }
+                    .toTypedArray()
+            )
+        return RegionTraceSubject(regionTrace, this)
+    }
+
+    /** Executes a custom [assertion] on the current subject */
+    operator fun invoke(
+        name: String,
+        isOptional: Boolean = false,
+        assertion: (LayerTraceEntrySubject) -> Unit
+    ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
+
+    fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject =
+        hasFrameSequence(ComponentNameMatcher("", name), frameNumbers)
+
+    fun hasFrameSequence(
+        componentMatcher: IComponentMatcher,
+        frameNumbers: Iterable<Long>
+    ): LayersTraceSubject = apply {
+        val firstFrame = frameNumbers.first()
+        val entries =
+            trace.entries
+                .asSequence()
+                // map entry to buffer layers with name
+                .map { it.getLayerWithBuffer(componentMatcher) }
+                // removing all entries without the layer
+                .filterNotNull()
+                // removing all entries with the same frame number
+                .distinctBy { it.currFrame }
+                // drop until the first frame we are interested in
+                .dropWhile { layer -> layer.currFrame != firstFrame }
+
+        var numFound = 0
+        val frameNumbersMatch =
+            entries
+                .zip(frameNumbers.asSequence()) { layer, frameNumber ->
+                    numFound++
+                    layer.currFrame == frameNumber
+                }
+                .all { it }
+        val allFramesFound = frameNumbers.count() == numFound
+        if (!allFramesFound || !frameNumbersMatch) {
+            val message =
+                "Could not find Layer:" +
+                    componentMatcher.toLayerIdentifier() +
+                    " with frame sequence:" +
+                    frameNumbers.joinToString(",") +
+                    " Found:\n" +
+                    entries.joinToString("\n")
+            fail(message)
+        }
+    }
+
+    /** Run the assertions for all trace entries within the specified time range */
+    fun forSystemUpTimeRange(startTime: Long, endTime: Long) {
+        val subjectsInRange =
+            subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime }
+        assertionsChecker.test(subjectsInRange)
+    }
+
+    /**
+     * User-defined entry point for the trace entry with [timestamp]
+     *
+     * @param timestamp of the entry
+     */
+    fun getEntryBySystemUpTime(
+        timestamp: Long,
+        byElapsedTimestamp: Boolean = false
+    ): LayerTraceEntrySubject {
+        return if (byElapsedTimestamp) {
+            subjects.first { it.entry.elapsedTimestamp == timestamp }
+        } else {
+            subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionSubject.kt
new file mode 100644
index 0000000..64c8c05
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionSubject.kt
@@ -0,0 +1,439 @@
+/*
+ * 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.region
+
+import android.tools.common.Timestamp
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.traces.region.RegionEntry
+import kotlin.math.abs
+
+/** Subject for [Rect] objects, used to make assertions over behaviors that occur on a rectangle. */
+class RegionSubject(
+    override val parent: FlickerSubject?,
+    val regionEntry: RegionEntry,
+    override val timestamp: Timestamp
+) : FlickerSubject() {
+
+    /** Custom constructor for existing android regions */
+    constructor(
+        region: Region?,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(parent, RegionEntry(region ?: Region.EMPTY, timestamp), timestamp)
+
+    /** Custom constructor for existing rects */
+    constructor(
+        rect: Array<Rect>,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(Region(rect), parent, timestamp)
+
+    /** Custom constructor for existing rects */
+    constructor(
+        rect: Rect?,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(Region.from(rect), parent, timestamp)
+
+    /** Custom constructor for existing rects */
+    constructor(
+        rect: RectF?,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(rect?.toRect(), parent, timestamp)
+
+    /** Custom constructor for existing rects */
+    constructor(
+        rect: Array<RectF>,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(mergeRegions(rect.map { Region.from(it.toRect()) }.toTypedArray()), parent, timestamp)
+
+    /** Custom constructor for existing regions */
+    constructor(
+        regions: Array<Region>,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(mergeRegions(regions), parent, timestamp)
+
+    /**
+     * Custom constructor
+     *
+     * @param regionEntry to assert
+     * @param parent containing the entry
+     */
+    constructor(
+        regionEntry: RegionEntry?,
+        parent: FlickerSubject? = null,
+        timestamp: Timestamp
+    ) : this(regionEntry?.region, parent, timestamp)
+
+    val region = regionEntry.region
+
+    private val Rect.area
+        get() = this.width * this.height
+
+    override val selfFacts = listOf(Fact("Region - Covered", region.toString()))
+
+    /** {@inheritDoc} */
+    override fun fail(reason: List<Fact>): FlickerSubject {
+        val newReason = reason.toMutableList()
+        return super.fail(newReason)
+    }
+
+    /** Asserts that the current [Region] doesn't contain layers */
+    fun isEmpty(): RegionSubject = apply { check(regionEntry.region.isEmpty) { "Region is empty" } }
+
+    /** Asserts that the current [Region] doesn't contain layers */
+    fun isNotEmpty(): RegionSubject = apply {
+        check(regionEntry.region.isNotEmpty) { "Region is not empty" }
+    }
+
+    private fun assertLeftRightAndAreaEquals(other: Region) {
+        check { MSG_ERROR_LEFT_POSITION }.that(region.bounds.left).isEqual(other.bounds.left)
+        check { MSG_ERROR_RIGHT_POSITION }.that(region.bounds.right).isEqual(other.bounds.right)
+        check { MSG_ERROR_AREA }.that(region.bounds.area).isEqual(other.bounds.area)
+    }
+
+    /** Subtracts [other] from this subject [region] */
+    fun minus(other: Region): RegionSubject {
+        val remainingRegion = Region.from(this.region)
+        remainingRegion.op(other, Region.Op.XOR)
+        return RegionSubject(remainingRegion, this, timestamp)
+    }
+
+    /** Adds [other] to this subject [region] */
+    fun plus(other: Region): RegionSubject {
+        val remainingRegion = Region.from(this.region)
+        remainingRegion.op(other, Region.Op.UNION)
+        return RegionSubject(remainingRegion, this, timestamp)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller or equal to
+     * those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigherOrEqual(subject: RegionSubject): RegionSubject = apply {
+        isHigherOrEqual(subject.region)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are smaller or equal to those of
+     * [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigherOrEqual(other: Rect): RegionSubject = apply { isHigherOrEqual(Region.from(other)) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are smaller or equal to those of
+     * [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigherOrEqual(other: Region): RegionSubject = apply {
+        assertLeftRightAndAreaEquals(other)
+        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isLowerOrEqual(other.bounds.top)
+        check { MSG_ERROR_BOTTOM_POSITION }
+            .that(region.bounds.bottom)
+            .isLowerOrEqual(other.bounds.bottom)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater or equal to
+     * those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLowerOrEqual(subject: RegionSubject): RegionSubject = apply {
+        isLowerOrEqual(subject.region)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are greater or equal to those of
+     * [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLowerOrEqual(other: Rect): RegionSubject = apply { isLowerOrEqual(Region.from(other)) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are greater or equal to those of
+     * [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLowerOrEqual(other: Region): RegionSubject = apply {
+        assertLeftRightAndAreaEquals(other)
+        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isGreaterOrEqual(other.bounds.top)
+        check { MSG_ERROR_BOTTOM_POSITION }
+            .that(region.bounds.bottom)
+            .isGreaterOrEqual(other.bounds.bottom)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller than those
+     * of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigher(subject: RegionSubject): RegionSubject = apply { isHigher(subject.region) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are smaller than those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigher(other: Rect): RegionSubject = apply { isHigher(Region.from(other)) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are smaller than those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isHigher(other: Region): RegionSubject = apply {
+        assertLeftRightAndAreaEquals(other)
+        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isLower(other.bounds.top)
+        check { MSG_ERROR_BOTTOM_POSITION }.that(region.bounds.bottom).isLower(other.bounds.bottom)
+    }
+
+    /**
+     * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater than those
+     * of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLower(subject: RegionSubject): RegionSubject = apply { isLower(subject.region) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are greater than those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLower(other: Rect): RegionSubject = apply { isLower(Region.from(other)) }
+
+    /**
+     * Asserts that the top and bottom coordinates of [other] are greater than those of [region].
+     *
+     * Also checks that the left and right positions, as well as area, don't change
+     */
+    fun isLower(other: Region): RegionSubject = apply {
+        assertLeftRightAndAreaEquals(other)
+        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isGreater(other.bounds.top)
+        check { MSG_ERROR_BOTTOM_POSITION }
+            .that(region.bounds.bottom)
+            .isGreater(other.bounds.bottom)
+    }
+
+    /**
+     * Asserts that [region] covers at most [testRegion], that is, its area doesn't cover any point
+     * outside of [testRegion].
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtMost(testRegion: Region): RegionSubject = apply {
+        if (!region.coversAtMost(testRegion)) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Covered region", region),
+                Fact("Out-of-bounds region", region.outOfBoundsRegion(testRegion))
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] covers at most [testRect], that is, its area doesn't cover any point
+     * outside of [testRect].
+     *
+     * @param testRect Expected covered area
+     */
+    fun coversAtMost(testRect: Rect): RegionSubject = apply { coversAtMost(Region.from(testRect)) }
+
+    /**
+     * Asserts that [region] is not bigger than [testRegion], even if the regions don't overlap.
+     *
+     * @param testRegion Area to compare to
+     */
+    fun notBiggerThan(testRegion: Region): RegionSubject = apply {
+        val testArea = testRegion.bounds.area
+        val area = region.bounds.area
+
+        if (area > testArea) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Area of test region", testArea),
+                Fact("Covered region", region),
+                Fact("Area of region", area)
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] is positioned to the right and bottom from [testRegion], but the
+     * regions can overlap and [region] can be smaller than [testRegion]
+     *
+     * @param testRegion Area to compare to
+     * @param threshold Offset threshold by which the position might be off
+     */
+    fun isToTheRightBottom(testRegion: Region, threshold: Int): RegionSubject = apply {
+        val horizontallyPositionedToTheRight =
+            testRegion.bounds.left - threshold <= region.bounds.left
+        val verticallyPositionedToTheBottom = testRegion.bounds.top - threshold <= region.bounds.top
+
+        if (!horizontallyPositionedToTheRight || !verticallyPositionedToTheBottom) {
+            fail(Fact("Region to test", testRegion), Fact("Actual region", region))
+        }
+    }
+
+    /**
+     * Asserts that [region] covers at least [testRegion], that is, its area covers each point in
+     * the region
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtLeast(testRegion: Region): RegionSubject = apply {
+        if (!region.coversAtLeast(testRegion)) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Covered region", region),
+                Fact("Uncovered region", region.uncoveredRegion(testRegion))
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] covers at least [testRect], that is, its area covers each point in the
+     * region
+     *
+     * @param testRect Expected covered area
+     */
+    fun coversAtLeast(testRect: Rect): RegionSubject = apply {
+        coversAtLeast(Region.from(testRect))
+    }
+
+    /**
+     * Asserts that [region] covers at exactly [testRegion]
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversExactly(testRegion: Region): RegionSubject = apply {
+        val intersection = Region.from(region)
+        val isNotEmpty = intersection.op(testRegion, Region.Op.XOR)
+
+        if (isNotEmpty) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Covered region", region),
+                Fact("Uncovered region", intersection)
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] covers at exactly [testRect]
+     *
+     * @param testRect Expected covered area
+     */
+    fun coversExactly(testRect: Rect): RegionSubject = apply {
+        coversExactly(Region.from(testRect))
+    }
+
+    /**
+     * Asserts that [region] and [testRegion] overlap
+     *
+     * @param testRegion Other area
+     */
+    fun overlaps(testRegion: Region): RegionSubject = apply {
+        val intersection = Region.from(region)
+        val isEmpty = !intersection.op(testRegion, Region.Op.INTERSECT)
+
+        if (isEmpty) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Covered region", region),
+                Fact("Overlap region", intersection)
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] and [testRect] overlap
+     *
+     * @param testRect Other area
+     */
+    fun overlaps(testRect: Rect): RegionSubject = apply { overlaps(Region.from(testRect)) }
+
+    /**
+     * Asserts that [region] and [testRegion] don't overlap
+     *
+     * @param testRegion Other area
+     */
+    fun notOverlaps(testRegion: Region): RegionSubject = apply {
+        val intersection = Region.from(region)
+        val isEmpty = !intersection.op(testRegion, Region.Op.INTERSECT)
+
+        if (!isEmpty) {
+            fail(
+                Fact("Region to test", testRegion),
+                Fact("Covered region", region),
+                Fact("Overlap region", intersection)
+            )
+        }
+    }
+
+    /**
+     * Asserts that [region] and [testRect] don't overlap
+     *
+     * @param testRect Other area
+     */
+    fun notOverlaps(testRect: Rect): RegionSubject = apply { notOverlaps(Region.from(testRect)) }
+
+    /**
+     * Asserts that [region] and [other] have same aspect ratio, margin of error up to 0.1.
+     *
+     * @param other Other region
+     */
+    fun isSameAspectRatio(other: RegionSubject): RegionSubject = apply {
+        val aspectRatio = this.region.width.toFloat() / this.region.height
+        val otherAspectRatio = other.region.width.toFloat() / other.region.height
+        check { "Aspect Ratio Difference" }
+            .that(abs(aspectRatio - otherAspectRatio))
+            .isLowerOrEqual(0.1f)
+    }
+
+    companion object {
+        const val MSG_ERROR_TOP_POSITION = "Top position"
+        const val MSG_ERROR_BOTTOM_POSITION = "Bottom position"
+        const val MSG_ERROR_LEFT_POSITION = "Left position"
+        const val MSG_ERROR_RIGHT_POSITION = "Right position"
+        const val MSG_ERROR_AREA = "Rect area"
+
+        private fun mergeRegions(regions: Array<Region>): Region {
+            val result = Region.EMPTY
+            regions.forEach { region ->
+                region.rects.forEach { rect -> result.op(rect, Region.Op.UNION) }
+            }
+            return result
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionTraceSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionTraceSubject.kt
new file mode 100644
index 0000000..4616b52
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/region/RegionTraceSubject.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.region
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import android.tools.common.traces.region.RegionTrace
+
+class RegionTraceSubject(val trace: RegionTrace, override val parent: FlickerSubject?) :
+    FlickerTraceSubject<RegionSubject>() {
+
+    override val subjects by lazy { trace.entries.map { RegionSubject(it, this, it.timestamp) } }
+
+    private val componentsAsString =
+        if (trace.components == null) {
+            "<any>"
+        } else {
+            "[${trace.components}]"
+        }
+
+    /**
+     * Asserts that the visible area covered by any element in the state covers at most [testRegion]
+     * , that is, if the area of no elements cover any point outside of [testRegion].
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtMost(testRegion: Region): RegionTraceSubject = apply {
+        addAssertion("coversAtMost($testRegion, $componentsAsString") {
+            it.coversAtMost(testRegion)
+        }
+    }
+
+    /**
+     * Asserts that the visible area covered by any element in the state covers at most [testRegion]
+     * , that is, if the area of no elements cover any point outside of [testRegion].
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtMost(testRegion: Rect): RegionTraceSubject = this.coversAtMost(testRegion)
+
+    /**
+     * Asserts that the visible area covered by any element in the state covers at least
+     * [testRegion], that is, if the area of its elements visible region covers each point in the
+     * region.
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtLeast(testRegion: Region): RegionTraceSubject = apply {
+        addAssertion("coversAtLeast($testRegion, $componentsAsString)") {
+            it.coversAtLeast(testRegion)
+        }
+    }
+
+    /**
+     * Asserts that the visible area covered by any element in the state covers at least
+     * [testRegion], that is, if the area of its elements visible region covers each point in the
+     * region.
+     *
+     * @param testRegion Expected covered area
+     */
+    fun coversAtLeast(testRegion: Rect): RegionTraceSubject =
+        this.coversAtLeast(Region.from(testRegion))
+
+    /**
+     * Asserts that the visible region of the trace entries is exactly [expectedVisibleRegion].
+     *
+     * @param expectedVisibleRegion Expected visible region of the layer
+     */
+    fun coversExactly(expectedVisibleRegion: Region): RegionTraceSubject = apply {
+        addAssertion("coversExactly($expectedVisibleRegion, $componentsAsString)") {
+            it.coversExactly(expectedVisibleRegion)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/wm/IWindowManagerSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/wm/IWindowManagerSubject.kt
new file mode 100644
index 0000000..b7440a8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/wm/IWindowManagerSubject.kt
@@ -0,0 +1,275 @@
+/*
+ * 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.PlatformConsts
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.WindowState
+
+/** Base interface for WM trace and state assertions */
+interface IWindowManagerSubject<WMSubjectType, RegionSubjectType> {
+    /** Asserts the current WindowManager state doesn't contain [WindowState]s */
+    fun isEmpty(): WMSubjectType
+
+    /** Asserts the current WindowManager state contains [WindowState]s */
+    fun isNotEmpty(): WMSubjectType
+
+    /**
+     * Obtains the region occupied by all windows matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    fun visibleRegion(componentMatcher: IComponentMatcher? = null): RegionSubjectType
+
+    /**
+     * Asserts the state contains a [WindowState] matching [componentMatcher] above the app windows
+     *
+     * @param componentMatcher Component to search
+     */
+    fun containsAboveAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state contains a [WindowState] matching [componentMatcher] below the app windows
+     *
+     * @param componentMatcher Component to search
+     */
+    fun containsBelowAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state contains [WindowState]s matching [aboveWindowComponentMatcher] and
+     * [belowWindowComponentMatcher], and that [aboveWindowComponentMatcher] is above
+     * [belowWindowComponentMatcher]
+     *
+     * This assertion can be used, for example, to assert that a PIP window is shown above other
+     * apps.
+     *
+     * @param aboveWindowComponentMatcher name of the window that should be above
+     * @param belowWindowComponentMatcher name of the window that should be below
+     */
+    fun isAboveWindow(
+        aboveWindowComponentMatcher: IComponentMatcher,
+        belowWindowComponentMatcher: IComponentMatcher
+    ): WMSubjectType
+
+    /**
+     * Asserts the state contains a non-app [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Component to search
+     */
+    fun containsNonAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the top visible app window in the state matches [componentMatcher]
+     *
+     * @param componentMatcher Component to search
+     */
+    fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the top visible app window in the state doesn't match [componentMatcher]
+     *
+     * @param componentMatcher Component to search
+     */
+    fun isAppWindowNotOnTop(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the bounds of the [WindowState] matching [componentMatcher] don't overlap.
+     *
+     * @param componentMatcher Component to search
+     */
+    fun doNotOverlap(vararg componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state contains an app [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Component to search
+     */
+    fun containsAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the display with id [displayId] has rotation [rotation]
+     *
+     * @param rotation to assert
+     * @param displayId of the target display
+     */
+    fun hasRotation(
+        rotation: Rotation,
+        displayId: Int = PlatformConsts.DEFAULT_DISPLAY
+    ): WMSubjectType
+
+    /**
+     * Asserts the state contains a [WindowState] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun contains(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state doesn't contain a [WindowState] nor an [Activity] matching
+     * [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun notContainsAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state doesn't contain a [WindowState] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun notContains(componentMatcher: IComponentMatcher): WMSubjectType
+
+    fun isRecentsActivityVisible(): WMSubjectType
+
+    fun isRecentsActivityInvisible(): WMSubjectType
+
+    /**
+     * Asserts the state is valid, that is, if it has:
+     * - a resumed activity
+     * - a focused activity
+     * - a focused window
+     * - a front window
+     * - a focused app
+     */
+    fun isValid(): WMSubjectType
+
+    /**
+     * Asserts the state contains a visible [WindowState] matching [componentMatcher].
+     *
+     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
+     * that it contains a visible [Activity] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isNonAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state contains a visible [WindowState] matching [componentMatcher].
+     *
+     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
+     * that it contains a visible [Activity] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /** Asserts the state contains no visible app windows. */
+    fun hasNoVisibleAppWindow(): WMSubjectType
+
+    /** Asserts the state contains no visible app windows. */
+    fun isKeyguardShowing(): WMSubjectType
+
+    /**
+     * Asserts the state contains an invisible window [WindowState] matching [componentMatcher].
+     *
+     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
+     * that it contains an invisible [Activity] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts the state contains an invisible window matching [componentMatcher].
+     *
+     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
+     * that it contains an invisible [Activity] matching [componentMatcher].
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isNonAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /** Asserts the state home activity is visible */
+    fun isHomeActivityVisible(): WMSubjectType
+
+    /** Asserts the state home activity is invisible */
+    fun isHomeActivityInvisible(): WMSubjectType
+
+    /**
+     * Asserts that [app] is the focused app
+     *
+     * @param app App to check
+     */
+    fun isFocusedApp(app: String): WMSubjectType
+
+    /**
+     * Asserts that [app] is not the focused app
+     *
+     * @param app App to check
+     */
+    fun isNotFocusedApp(app: String): WMSubjectType
+
+    /**
+     * Asserts that [componentMatcher] exists and is pinned (in PIP mode)
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isPinned(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Asserts that [componentMatcher] exists and is not pinned (not in PIP mode)
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isNotPinned(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Checks if the activity with matching [componentMatcher] is visible
+     *
+     * In the case that an app is stopped in the background (e.g. OS stopped it to release memory)
+     * the app window will not be immediately visible when switching back to the app. Checking if a
+     * snapshotStartingWindow is present for that app instead can decrease flakiness levels of the
+     * assertion.
+     *
+     * @param componentMatcher Component to search
+     */
+    fun isAppSnapshotStartingWindowVisibleFor(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Checks if the non-app window matching [componentMatcher] exists above the app windows and is
+     * visible
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isAboveAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Checks if the non-app window matching [componentMatcher] exists above the app windows and is
+     * invisible
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isAboveAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Checks if the non-app window matching [componentMatcher] exists below the app windows and is
+     * visible
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isBelowAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
+
+    /**
+     * Checks if the non-app window matching [componentMatcher] exists below the app windows and is
+     * invisible
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isBelowAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubject.kt
new file mode 100644
index 0000000..69a2b9d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubject.kt
@@ -0,0 +1,553 @@
+/*
+ * 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.Region
+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.FlickerSubject
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowState
+
+/**
+ * Subject for [WindowManagerState] objects, used to make assertions over behaviors that occur on a
+ * single WM state.
+ *
+ * To make assertions over a specific state from a trace it is recommended to create a subject using
+ * [WindowManagerTraceSubject](myTrace) and select the specific state using:
+ * ```
+ *     [WindowManagerTraceSubject.first]
+ *     [WindowManagerTraceSubject.last]
+ *     [WindowManagerTraceSubject.entry]
+ * ```
+ * Alternatively, it is also possible to use [WindowManagerStateSubject](myState).
+ *
+ * Example:
+ * ```
+ *    val trace = WindowManagerTraceParser().parse(myTraceFile)
+ *    val subject = WindowManagerTraceSubject(trace).first()
+ *        .contains("ValidWindow")
+ *        .notContains("ImaginaryWindow")
+ *        .showsAboveAppWindow("NavigationBar")
+ *        .invoke { myCustomAssertion(this) }
+ * ```
+ */
+class WindowManagerStateSubject(
+    val wmState: WindowManagerState,
+    val trace: WindowManagerTraceSubject? = null,
+    override val parent: FlickerSubject? = null
+) : FlickerSubject(), IWindowManagerSubject<WindowManagerStateSubject, RegionSubject> {
+    override val timestamp = wmState.timestamp
+    override val selfFacts = listOf(Fact("WM State", wmState))
+
+    val subjects by lazy { wmState.windowStates.map { WindowStateSubject(this, timestamp, it) } }
+
+    val appWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.appWindows.contains(it.windowState) }
+
+    val nonAppWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.nonAppWindows.contains(it.windowState) }
+
+    val aboveAppWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.aboveAppWindows.contains(it.windowState) }
+
+    val belowAppWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.belowAppWindows.contains(it.windowState) }
+
+    val visibleWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.visibleWindows.contains(it.windowState) }
+
+    val visibleAppWindows: List<WindowStateSubject>
+        get() = subjects.filter { wmState.visibleAppWindows.contains(it.windowState) }
+
+    /** Executes a custom [assertion] on the current subject */
+    operator fun invoke(assertion: (WindowManagerState) -> Unit): WindowManagerStateSubject =
+        apply {
+            assertion(this.wmState)
+        }
+
+    /** {@inheritDoc} */
+    override fun isEmpty(): WindowManagerStateSubject = apply {
+        check { "WM state is empty" }.that(subjects.isEmpty()).isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isNotEmpty(): WindowManagerStateSubject = apply {
+        check { "WM state is not empty" }.that(subjects.isEmpty()).isEqual(false)
+    }
+
+    /** {@inheritDoc} */
+    override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionSubject {
+        val selectedWindows =
+            if (componentMatcher == null) {
+                // No filters so use all subjects
+                subjects
+            } else {
+                subjects.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
+            }
+
+        if (selectedWindows.isEmpty()) {
+            val str = componentMatcher?.toWindowIdentifier() ?: "<any>"
+            fail(Fact(ASSERTION_TAG, "visibleRegion($str)"), Fact("Could not find windows", str))
+        }
+
+        val visibleWindows = selectedWindows.filter { it.isVisible }
+        val visibleRegions = visibleWindows.map { it.windowState.frameRegion }.toTypedArray()
+        return RegionSubject(visibleRegions, this, timestamp)
+    }
+
+    /** {@inheritDoc} */
+    override fun containsAboveAppWindow(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { contains(aboveAppWindows, componentMatcher) }
+
+    /** {@inheritDoc} */
+    override fun containsBelowAppWindow(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { contains(belowAppWindows, componentMatcher) }
+
+    /** {@inheritDoc} */
+    override fun isAboveWindow(
+        aboveWindowComponentMatcher: IComponentMatcher,
+        belowWindowComponentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        contains(aboveWindowComponentMatcher)
+        contains(belowWindowComponentMatcher)
+
+        val aboveWindow =
+            wmState.windowStates.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it) }
+        val belowWindow =
+            wmState.windowStates.first { belowWindowComponentMatcher.windowMatchesAnyOf(it) }
+        if (aboveWindow == belowWindow) {
+            fail(
+                Fact(
+                    ASSERTION_TAG,
+                    "Above and below windows should be different. " +
+                        "Instead they were ${aboveWindow.title} " +
+                        "(matched with ${aboveWindowComponentMatcher.toWindowIdentifier()} and " +
+                        "${belowWindowComponentMatcher.toWindowIdentifier()})"
+                )
+            )
+        }
+
+        // windows are ordered by z-order, from top to bottom
+        val aboveZ =
+            wmState.windowStates.indexOfFirst { aboveWindowComponentMatcher.windowMatchesAnyOf(it) }
+        val belowZ =
+            wmState.windowStates.indexOfFirst { belowWindowComponentMatcher.windowMatchesAnyOf(it) }
+        if (aboveZ >= belowZ) {
+            val matchedAboveWindow =
+                subjects.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it.windowState) }
+            val aboveWindowTitleStr = aboveWindowComponentMatcher.toWindowIdentifier()
+            val belowWindowTitleStr = belowWindowComponentMatcher.toWindowIdentifier()
+            matchedAboveWindow.fail(
+                Fact(
+                    ASSERTION_TAG,
+                    "isAboveWindow(above=$aboveWindowTitleStr, below=$belowWindowTitleStr"
+                ),
+                Fact("Above", aboveWindowTitleStr),
+                Fact("Below", belowWindowTitleStr)
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun containsNonAppWindow(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { contains(nonAppWindows, componentMatcher) }
+
+    /** {@inheritDoc} */
+    override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
+        apply {
+            if (wmState.visibleAppWindows.isEmpty()) {
+                fail(
+                    Fact(
+                        ASSERTION_TAG,
+                        "isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})"
+                    ),
+                    Fact("Not found", "No visible app windows found")
+                )
+            }
+            val topVisibleAppWindow = wmState.topVisibleAppWindow
+            if (
+                topVisibleAppWindow == null ||
+                    !componentMatcher.windowMatchesAnyOf(topVisibleAppWindow)
+            ) {
+                isNotEmpty()
+                val topWindow = subjects.first { it.windowState == topVisibleAppWindow }
+                topWindow.fail(
+                    Fact(
+                        ASSERTION_TAG,
+                        "isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})"
+                    ),
+                    Fact("Not on top", componentMatcher.toWindowIdentifier()),
+                    Fact("Found", wmState.topVisibleAppWindow)
+                )
+            }
+        }
+
+    /** {@inheritDoc} */
+    override fun isAppWindowNotOnTop(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        val topVisibleAppWindow = wmState.topVisibleAppWindow
+        if (
+            topVisibleAppWindow != null && componentMatcher.windowMatchesAnyOf(topVisibleAppWindow)
+        ) {
+            val topWindow = subjects.first { it.windowState == topVisibleAppWindow }
+            topWindow.fail(
+                Fact(
+                    ASSERTION_TAG,
+                    "isAppWindowNotOnTop(${componentMatcher.toWindowIdentifier()})"
+                ),
+                Fact("On top", componentMatcher.toWindowIdentifier())
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun doNotOverlap(
+        vararg componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        check {
+                val repr = componentMatcher.joinToString(", ") { it.toWindowIdentifier() }
+                "Must give more than one window to check! (Given $repr)"
+            }
+            .that(componentMatcher.size)
+            .isEqual(1)
+
+        componentMatcher.forEach { contains(it) }
+        val foundWindows =
+            componentMatcher
+                .toSet()
+                .associateWith { act ->
+                    wmState.windowStates.firstOrNull { act.windowMatchesAnyOf(it) }
+                }
+                // keep entries only for windows that we actually found by removing nulls
+                .filterValues { it != null }
+        val foundWindowsRegions =
+            foundWindows.mapValues { (_, v) -> v?.frameRegion ?: Region.EMPTY }
+
+        val regions = foundWindowsRegions.entries.toList()
+        for (i in regions.indices) {
+            val (ourTitle, ourRegion) = regions[i]
+            for (j in i + 1 until regions.size) {
+                val (otherTitle, otherRegion) = regions[j]
+                if (ourRegion.op(otherRegion, Region.Op.INTERSECT)) {
+                    val window = foundWindows[ourTitle] ?: error("Window $ourTitle not found")
+                    val windowSubject = subjects.first { it.windowState == window }
+                    windowSubject.fail(
+                        Fact(
+                            ASSERTION_TAG,
+                            "noWindowsOverlap" +
+                                componentMatcher.joinToString { it.toWindowIdentifier() }
+                        ),
+                        Fact("Overlap", ourTitle),
+                        Fact("Overlap", otherTitle)
+                    )
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
+        apply {
+            // Check existence of activity
+            val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull()
+            check { "\"Activity for window ${componentMatcher.toWindowIdentifier()} exists" }
+                .that(activity)
+                .isNotNull()
+
+            // Check existence of window.
+            contains(componentMatcher)
+        }
+
+    /** {@inheritDoc} */
+    override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerStateSubject =
+        apply {
+            check { "hasRotation" }.that(wmState.getRotation(displayId)).isEqual(rotation)
+        }
+
+    /** {@inheritDoc} */
+    override fun contains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply {
+        contains(subjects, componentMatcher)
+    }
+
+    /** {@inheritDoc} */
+    override fun notContainsAppWindow(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name
+        // nor an activity, ignore them
+        check { "Activity '${componentMatcher.toActivityIdentifier()}' does not exist" }
+            .that(wmState.containsActivity(componentMatcher))
+            .isEqual(false)
+        notContains(componentMatcher)
+    }
+
+    /** {@inheritDoc} */
+    override fun notContains(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
+        apply {
+            check { "Window '${componentMatcher.toWindowIdentifier()}' does not exist" }
+                .that(wmState.containsWindow(componentMatcher))
+                .isEqual(false)
+        }
+
+    /** {@inheritDoc} */
+    override fun isRecentsActivityVisible(): WindowManagerStateSubject = apply {
+        if (wmState.isHomeRecentsComponent) {
+            isHomeActivityVisible()
+        } else {
+            check { "Recents activity is visible" }
+                .that(wmState.isRecentsActivityVisible)
+                .isEqual(true)
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun isRecentsActivityInvisible(): WindowManagerStateSubject = apply {
+        if (wmState.isHomeRecentsComponent) {
+            isHomeActivityInvisible()
+        } else {
+            check { "Recents activity is not visible" }
+                .that(wmState.isRecentsActivityVisible)
+                .isEqual(false)
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun isValid(): WindowManagerStateSubject = apply {
+        check { "Stacks count" }.that(wmState.stackCount).isGreater(0)
+        // TODO: Update when keyguard will be shown on multiple displays
+        if (!wmState.keyguardControllerState.isKeyguardShowing) {
+            check { "Resumed activity" }.that(wmState.resumedActivitiesCount).isGreater(0)
+        }
+        check { "No focused activity" }.that(wmState.focusedActivity).isNotEqual(null)
+        wmState.rootTasks.forEach { aStack ->
+            val stackId = aStack.rootTaskId
+            aStack.tasks.forEach { aTask ->
+                check { "Root task Id for stack $aTask" }.that(stackId).isEqual(aTask.rootTaskId)
+            }
+        }
+        check { "Front window" }.that(wmState.frontWindow).isNotEqual(null)
+        check { "Focused window" }.that(wmState.focusedWindow).isNotEqual(null)
+        check { "Focused app" }.that(wmState.focusedApp.isNotEmpty()).isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isNonAppWindowVisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { checkWindowIsVisible(nonAppWindows, componentMatcher) }
+
+    /** {@inheritDoc} */
+    override fun isAppWindowVisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        containsAppWindow(componentMatcher)
+        checkWindowIsVisible(appWindows, componentMatcher)
+    }
+
+    /** {@inheritDoc} */
+    override fun hasNoVisibleAppWindow(): WindowManagerStateSubject = apply {
+        if (visibleAppWindows.isNotEmpty()) {
+            val visibleAppWindows = visibleAppWindows.joinToString { it.name }
+            fail(
+                Fact(ASSERTION_TAG, "hasNoVisibleAppWindow()"),
+                Fact("Found visible windows", visibleAppWindows)
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun isKeyguardShowing(): WindowManagerStateSubject = apply {
+        if (!wmState.isKeyguardShowing && !wmState.isAodShowing) {
+            fail(
+                Fact(ASSERTION_TAG, "isKeyguardShowing()"),
+                Fact("Keyguard showing", wmState.isKeyguardShowing)
+            )
+        }
+    }
+
+    /** {@inheritDoc} */
+    override fun isAppWindowInvisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { checkWindowIsInvisible(appWindows, componentMatcher) }
+
+    /** {@inheritDoc} */
+    override fun isNonAppWindowInvisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply { checkWindowIsInvisible(nonAppWindows, componentMatcher) }
+
+    private fun checkWindowIsVisible(
+        subjectList: List<WindowStateSubject>,
+        componentMatcher: IComponentMatcher
+    ) {
+        // Check existence of window.
+        contains(subjectList, componentMatcher)
+
+        val foundWindows =
+            subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
+
+        val visibleWindows =
+            wmState.visibleWindows.filter { visibleWindow ->
+                foundWindows.any { it.windowState == visibleWindow }
+            }
+
+        if (visibleWindows.isEmpty()) {
+            val windowId = componentMatcher.toWindowIdentifier()
+            val facts =
+                listOf(Fact(ASSERTION_TAG, "isVisible($windowId)"), Fact("Is Invisible", windowId))
+            foundWindows.first().fail(facts)
+        }
+    }
+
+    private fun checkWindowIsInvisible(
+        subjectList: List<WindowStateSubject>,
+        componentMatcher: IComponentMatcher
+    ) {
+        // Check existence of window.
+        contains(subjectList, componentMatcher)
+
+        val foundWindows =
+            subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
+
+        val visibleWindows =
+            wmState.visibleWindows.filter { visibleWindow ->
+                foundWindows.any { it.windowState == visibleWindow }
+            }
+
+        if (visibleWindows.isNotEmpty()) {
+            val windowId = componentMatcher.toWindowIdentifier()
+            val facts =
+                listOf(Fact(ASSERTION_TAG, "isInvisible($windowId)"), Fact("Is Visible", windowId))
+            foundWindows.first { it.windowState == visibleWindows.first() }.fail(facts)
+        }
+    }
+
+    private fun contains(
+        subjectList: List<WindowStateSubject>,
+        componentMatcher: IComponentMatcher
+    ) {
+        val windowStates = subjectList.map { it.windowState }
+        check { "Window '${componentMatcher.toWindowIdentifier()}' exists" }
+            .that(componentMatcher.windowMatchesAnyOf(windowStates))
+            .isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isHomeActivityVisible(): WindowManagerStateSubject = apply {
+        val homeIsVisible = wmState.homeActivity?.isVisible ?: false
+        check { "Home activity exists" }.that(wmState.homeActivity).isNotEqual(null)
+        check { "Home activity is visible" }.that(homeIsVisible).isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isHomeActivityInvisible(): WindowManagerStateSubject = apply {
+        val homeIsVisible = wmState.homeActivity?.isVisible ?: false
+        check { "Home activity is not visible" }.that(homeIsVisible).isEqual(false)
+    }
+
+    /** {@inheritDoc} */
+    override fun isFocusedApp(app: String): WindowManagerStateSubject = apply {
+        check { "Window is focused app $app" }.that(wmState.focusedApp).isEqual(app)
+    }
+
+    /** {@inheritDoc} */
+    override fun isNotFocusedApp(app: String): WindowManagerStateSubject = apply {
+        check { "Window is not focused app $app" }.that(wmState.focusedApp).isNotEqual(app)
+    }
+
+    /** {@inheritDoc} */
+    override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply {
+        contains(componentMatcher)
+        check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" }
+            .that(wmState.isInPipMode(componentMatcher))
+            .isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
+        apply {
+            contains(componentMatcher)
+            check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" }
+                .that(wmState.isInPipMode(componentMatcher))
+                .isEqual(false)
+        }
+
+    /** {@inheritDoc} */
+    override fun isAppSnapshotStartingWindowVisibleFor(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject = apply {
+        val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull()
+        requireNotNull(activity) { "Activity for $componentMatcher not found" }
+
+        // Check existence and visibility of SnapshotStartingWindow
+        val snapshotStartingWindow =
+            activity.getWindows(ComponentNameMatcher.SNAPSHOT).firstOrNull()
+
+        check { "SnapshotStartingWindow does not exist for activity ${activity.name}" }
+            .that(snapshotStartingWindow)
+            .isNotEqual(null)
+        check { "Activity is visible" }.that(activity.isVisible).isEqual(true)
+        check { "SnapshotStartingWindow is visible" }
+            .that(snapshotStartingWindow?.isVisible ?: false)
+            .isEqual(true)
+    }
+
+    /** {@inheritDoc} */
+    override fun isAboveAppWindowVisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject =
+        containsAboveAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher)
+
+    /** {@inheritDoc} */
+    override fun isAboveAppWindowInvisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject =
+        containsAboveAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher)
+
+    /** {@inheritDoc} */
+    override fun isBelowAppWindowVisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject =
+        containsBelowAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher)
+
+    /** {@inheritDoc} */
+    override fun isBelowAppWindowInvisible(
+        componentMatcher: IComponentMatcher
+    ): WindowManagerStateSubject =
+        containsBelowAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher)
+
+    /** Obtains the first subject with [WindowState.title] containing [name]. */
+    fun windowState(name: String): WindowStateSubject? = windowState { it.name.contains(name) }
+
+    /**
+     * Obtains the first subject matching [predicate].
+     *
+     * @param predicate to search for a subject
+     */
+    fun windowState(predicate: (WindowState) -> Boolean): WindowStateSubject? =
+        subjects.firstOrNull { predicate(it.windowState) }
+
+    override fun toString(): String {
+        return "WindowManagerStateSubject($wmState)"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubject.kt
new file mode 100644
index 0000000..0c29706
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubject.kt
@@ -0,0 +1,576 @@
+/*
+ * 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 }
+}
diff --git a/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowStateSubject.kt b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowStateSubject.kt
new file mode 100644
index 0000000..c89c860
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/flicker/subject/wm/WindowStateSubject.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.Timestamp
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.wm.WindowState
+
+/**
+ * Subject for [WindowState] objects, used to make assertions over behaviors that occur on a single
+ * [WindowState] of a WM state.
+ *
+ * To make assertions over a layer from a state it is recommended to create a subject using
+ * [WindowManagerStateSubject.windowState](windowStateName)
+ *
+ * Alternatively, it is also possible to use [WindowStateSubject](myWindow).
+ *
+ * Example:
+ * ```
+ *    val trace = WindowManagerTraceParser().parse(myTraceFile)
+ *    val subject = WindowManagerTraceSubject(trace).first()
+ *        .windowState("ValidWindow")
+ *        .exists()
+ *        { myCustomAssertion(this) }
+ * ```
+ */
+class WindowStateSubject(
+    override val parent: WindowManagerStateSubject,
+    override val timestamp: Timestamp,
+    val windowState: WindowState
+) : FlickerSubject() {
+    val isVisible: Boolean = windowState.isVisible
+    val isInvisible: Boolean = !windowState.isVisible
+    val name: String = windowState.name
+    val frame: RegionSubject
+        get() = RegionSubject(windowState.frame, this, timestamp)
+
+    override val selfFacts = listOf(Fact("Window title", windowState.title))
+
+    /** If the [windowState] exists, executes a custom [assertion] on the current subject */
+    operator fun invoke(assertion: (WindowState) -> Unit): WindowStateSubject = apply {
+        assertion(this.windowState)
+    }
+
+    override fun toString(): String {
+        return "WindowState:$name"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/io/Consts.kt b/libraries/flicker/src/android/tools/common/io/Consts.kt
new file mode 100644
index 0000000..9b057d6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/Consts.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.io
+
+import android.tools.common.FLICKER_TAG
+
+const val WINSCOPE_EXT = ".winscope"
+const val FLICKER_IO_TAG = "$FLICKER_TAG-IO"
+const val BUFFER_SIZE = 2048
diff --git a/libraries/flicker/src/android/tools/common/io/IReader.kt b/libraries/flicker/src/android/tools/common/io/IReader.kt
new file mode 100644
index 0000000..6a5c568
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/IReader.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.io
+
+import android.tools.common.Tag
+import android.tools.common.Timestamp
+import android.tools.common.traces.events.CujTrace
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+
+/** Helper class to read results from a flicker artifact */
+interface IReader {
+    val artifactPath: String
+    val executionError: Throwable?
+    val runStatus: RunStatus
+    val isFailure: Boolean
+        get() = runStatus.isFailure
+
+    /** @return a [WindowManagerTrace] from the dump associated to [tag] */
+    fun readWmState(tag: String): WindowManagerTrace?
+
+    /** @return a [WindowManagerTrace] for the part of the trace we want to run the assertions on */
+    fun readWmTrace(): WindowManagerTrace?
+
+    /** @return a [LayersTrace] for the part of the trace we want to run the assertions on */
+    fun readLayersTrace(): LayersTrace?
+
+    /** @return a [LayersTrace] from the dump associated to [tag] */
+    fun readLayersDump(tag: String): LayersTrace?
+
+    /** @return a [TransactionsTrace] for the part of the trace we want to run the assertions on */
+    fun readTransactionsTrace(): TransactionsTrace?
+
+    /** @return a [TransitionsTrace] for the part of the trace we want to run the assertions on */
+    fun readTransitionsTrace(): TransitionsTrace?
+
+    /** @return an [EventLog] for the part of the trace we want to run the assertions on */
+    fun readEventLogTrace(): EventLog?
+
+    /** @return a [CujTrace] for the part of the trace we want to run the assertions on */
+    fun readCujTrace(): CujTrace?
+
+    /** @return an [IReader] for the subsection of the trace we are reading in this reader */
+    fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): IReader
+
+    /**
+     * @return [ByteArray] with the contents of a file from the artifact, or null if the file
+     * doesn't exist
+     */
+    fun readBytes(traceType: TraceType, tag: String = Tag.ALL): ByteArray?
+}
diff --git a/libraries/flicker/src/android/tools/common/io/ResultArtifactDescriptor.kt b/libraries/flicker/src/android/tools/common/io/ResultArtifactDescriptor.kt
new file mode 100644
index 0000000..f4e9042
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/ResultArtifactDescriptor.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.io
+
+import android.tools.common.Tag
+
+/** Descriptor for files inside flicker result artifacts */
+class ResultArtifactDescriptor(
+    /** Trace or dump type */
+    val traceType: TraceType,
+    /** If the trace/dump is associated with a tag */
+    val tag: String = Tag.ALL
+) {
+    private val isTagTrace: Boolean
+        get() = tag != Tag.ALL
+
+    /** Name of the trace file in the result artifact (e.g. zip) */
+    val fileNameInArtifact: String = buildString {
+        if (isTagTrace) {
+            append(tag)
+            append("__")
+        }
+        append(traceType.fileName)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResultArtifactDescriptor) return false
+
+        if (traceType != other.traceType) return false
+        if (tag != other.tag) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = traceType.hashCode()
+        result = 31 * result + tag.hashCode()
+        return result
+    }
+
+    override fun toString(): String = fileNameInArtifact
+
+    companion object {
+        /**
+         * Creates a [ResultArtifactDescriptor] based on the [fileNameInArtifact]
+         *
+         * @param fileNameInArtifact Name of the trace file in the result artifact (e.g. zip)
+         */
+        fun fromFileName(fileNameInArtifact: String): ResultArtifactDescriptor {
+            val tagSplit = fileNameInArtifact.split("__")
+            require(tagSplit.size <= 2) {
+                "File name format should match '{tag}__{filename}' but was $fileNameInArtifact"
+            }
+            val tag = if (tagSplit.size > 1) tagSplit.first() else Tag.ALL
+            val fileName = tagSplit.last()
+            return ResultArtifactDescriptor(TraceType.fromFileName(fileName), tag)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/io/RunStatus.kt b/libraries/flicker/src/android/tools/common/io/RunStatus.kt
new file mode 100644
index 0000000..12d291c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/RunStatus.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.io
+
+/** Possible status of a flicker ru */
+enum class RunStatus(val prefix: String, val isFailure: Boolean) {
+    UNDEFINED("UNDEFINED", false),
+    RUN_EXECUTED("EXECUTED", false),
+    ASSERTION_SUCCESS("PASS", false),
+    RUN_FAILED("FAILED_RUN", true),
+    PARSING_FAILURE("FAILED_PARSING", true),
+    ASSERTION_FAILED("FAIL", true);
+
+    companion object {
+        fun fromFileName(fileName: String): RunStatus =
+            when (fileName.takeWhile { it != '_' }) {
+                RUN_EXECUTED.prefix -> RUN_EXECUTED
+                ASSERTION_SUCCESS.prefix -> ASSERTION_SUCCESS
+                RUN_FAILED.prefix -> RUN_FAILED
+                PARSING_FAILURE.prefix -> PARSING_FAILURE
+                ASSERTION_FAILED.prefix -> ASSERTION_FAILED
+                else -> UNDEFINED
+            }
+
+        val ALL: List<RunStatus> =
+            listOf(
+                UNDEFINED,
+                RUN_EXECUTED,
+                ASSERTION_SUCCESS,
+                RUN_FAILED,
+                PARSING_FAILURE,
+                ASSERTION_FAILED
+            )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/io/TraceType.kt b/libraries/flicker/src/android/tools/common/io/TraceType.kt
new file mode 100644
index 0000000..6a2ed3d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/TraceType.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.io
+
+/** Types of traces/dumps that cna be in a flicker result */
+enum class TraceType(val fileName: String, val isTrace: Boolean) {
+    SF("layers_trace$WINSCOPE_EXT", isTrace = true),
+    WM("wm_trace$WINSCOPE_EXT", isTrace = true),
+    TRANSACTION("transactions_trace$WINSCOPE_EXT", isTrace = true),
+    TRANSITION("transition_trace$WINSCOPE_EXT", isTrace = true),
+    EVENT_LOG("eventlog$WINSCOPE_EXT", isTrace = true),
+    SCREEN_RECORDING("transition.mp4", isTrace = true),
+    SF_DUMP("sf_dump$WINSCOPE_EXT", isTrace = false),
+    WM_DUMP("wm_dump$WINSCOPE_EXT", isTrace = false);
+
+    companion object {
+        fun fromFileName(fileName: String): TraceType {
+            return when {
+                fileName == SF.fileName -> SF
+                fileName == WM.fileName -> WM
+                fileName == TRANSACTION.fileName -> TRANSACTION
+                fileName == TRANSITION.fileName -> TRANSITION
+                fileName == SCREEN_RECORDING.fileName -> SCREEN_RECORDING
+                fileName.endsWith(SF_DUMP.fileName) -> SF_DUMP
+                fileName.endsWith(WM_DUMP.fileName) -> WM_DUMP
+                else -> error("Unknown trace type for fileName=$fileName")
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/io/TransitionTimeRange.kt b/libraries/flicker/src/android/tools/common/io/TransitionTimeRange.kt
new file mode 100644
index 0000000..0236f7a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/io/TransitionTimeRange.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.io
+
+import android.tools.common.Timestamp
+
+/** Representation of a transition interval */
+data class TransitionTimeRange(val start: Timestamp, val end: Timestamp)
diff --git a/libraries/flicker/src/android/tools/common/parsers/AbstractParser.kt b/libraries/flicker/src/android/tools/common/parsers/AbstractParser.kt
new file mode 100644
index 0000000..a85c70b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/parsers/AbstractParser.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.parsers
+
+import android.tools.common.Cache
+import android.tools.common.Timestamp
+import kotlin.js.JsName
+
+/** Base parser class */
+abstract class AbstractParser<InputTypeTrace, OutputTypeTrace> {
+    protected abstract val traceName: String
+    protected abstract fun doDecodeByteArray(bytes: ByteArray): InputTypeTrace
+    protected abstract fun doParse(input: InputTypeTrace): OutputTypeTrace
+
+    /**
+     * Uses a [ByteArray] to generates a trace
+     *
+     * @param bytes Parsed proto data
+     * @param clearCache If the caching used while parsing the object should be cleared
+     */
+    @JsName("parse")
+    open fun parse(bytes: ByteArray, clearCache: Boolean = true): OutputTypeTrace {
+        val input = decodeByteArray(bytes)
+        return parse(input, clearCache)
+    }
+
+    /**
+     * Uses [InputTypeTrace] to generates a trace
+     *
+     * @param input Parsed proto data
+     * @param clearCache If the caching used while parsing the object should be cleared
+     */
+    open fun parse(input: InputTypeTrace, clearCache: Boolean): OutputTypeTrace {
+        return try {
+            doParse(input)
+        } finally {
+            if (clearCache) {
+                Cache.clear()
+            }
+        }
+    }
+
+    protected fun decodeByteArray(input: ByteArray): InputTypeTrace {
+        return doDecodeByteArray(input)
+    }
+
+    fun getTimestampsInRange(
+        entries: List<Timestamp>,
+        from: Timestamp,
+        to: Timestamp,
+        addInitialEntry: Boolean
+    ): Set<Timestamp> {
+        require(from <= to) { "`from` must be smaller or equal to `to` but was $from and $to" }
+
+        return when {
+            entries.isEmpty() -> {
+                emptySet()
+            }
+            to < entries.first() -> {
+                // Slice before all entries
+                emptySet()
+            }
+            entries.last() < from -> {
+                // Slice after all entries
+                if (addInitialEntry) {
+                    // Keep the last entry as the start entry of the sliced trace
+                    setOf(entries.last())
+                } else {
+                    emptySet()
+                }
+            }
+            else -> {
+                // first entry <= to
+                // last entry >= from
+                // -----|--------|------
+                //      [   to     to
+                //  from    from ]
+
+                var first = entries.indexOfFirst { it >= from }
+                require(first >= 0) { "No match found for first index" }
+                val last = entries.lastIndex - entries.reversed().indexOfFirst { it <= to }
+                require(last >= 0) { "No match found for last index" }
+
+                if (addInitialEntry && first > 0 && entries[first] > from) {
+                    // Include previous state since from timestamp is in between the previous
+                    // one and first, and the previous state is the state we were still at a
+                    // timestamp from.
+                    first--
+                }
+
+                entries.slice(first..last).toSet()
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/parsers/AbstractTraceParser.kt b/libraries/flicker/src/android/tools/common/parsers/AbstractTraceParser.kt
new file mode 100644
index 0000000..0d99cee
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/parsers/AbstractTraceParser.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.parsers
+
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+
+/** Base trace parser class */
+abstract class AbstractTraceParser<
+    InputTypeTrace, InputTypeEntry, OutputTypeEntry, OutputTypeTrace> :
+    AbstractParser<InputTypeTrace, OutputTypeTrace>() {
+    protected abstract fun onBeforeParse(input: InputTypeTrace)
+    protected abstract fun getEntries(input: InputTypeTrace): List<InputTypeEntry>
+    protected abstract fun getTimestamp(entry: InputTypeEntry): Timestamp
+    protected abstract fun doParseEntry(entry: InputTypeEntry): OutputTypeEntry
+    protected abstract fun createTrace(entries: List<OutputTypeEntry>): OutputTypeTrace
+
+    open fun shouldParseEntry(entry: InputTypeEntry) = true
+
+    override fun parse(bytes: ByteArray, clearCache: Boolean): OutputTypeTrace {
+        return parse(
+            bytes,
+            from = CrossPlatform.timestamp.min(),
+            to = CrossPlatform.timestamp.max(),
+            addInitialEntry = true,
+            clearCache = clearCache
+        )
+    }
+
+    override fun parse(input: InputTypeTrace, clearCache: Boolean): OutputTypeTrace {
+        return parse(
+            input,
+            from = CrossPlatform.timestamp.min(),
+            to = CrossPlatform.timestamp.max(),
+            addInitialEntry = true,
+            clearCache = clearCache
+        )
+    }
+
+    override fun doParse(input: InputTypeTrace): OutputTypeTrace {
+        return doParse(
+            input,
+            from = CrossPlatform.timestamp.min(),
+            to = CrossPlatform.timestamp.max(),
+            addInitialEntry = true
+        )
+    }
+
+    /**
+     * Uses [InputTypeTrace] to generates a trace
+     *
+     * @param input Parsed proto data
+     * @param from Initial timestamp to be parsed
+     * @param to Final timestamp to be parsed
+     * @param addInitialEntry If the last entry smaller than [from] should be included as well
+     */
+    private fun doParse(
+        input: InputTypeTrace,
+        from: Timestamp,
+        to: Timestamp,
+        addInitialEntry: Boolean
+    ): OutputTypeTrace {
+        onBeforeParse(input)
+        val parsedEntries = mutableListOf<OutputTypeEntry>()
+        val rawEntries = getEntries(input)
+        val allInputTimestamps = rawEntries.map { getTimestamp(it) }
+        val selectedInputTimestamps =
+            getTimestampsInRange(allInputTimestamps, from, to, addInitialEntry)
+        for (rawEntry in rawEntries) {
+            val currTimestamp = getTimestamp(rawEntry)
+            if (!selectedInputTimestamps.contains(currTimestamp) || !shouldParseEntry(rawEntry)) {
+                continue
+            }
+            val parsedEntry =
+                CrossPlatform.log.withTracing("doParseEntry") { doParseEntry(rawEntry) }
+            parsedEntries.add(parsedEntry)
+        }
+        return createTrace(parsedEntries)
+    }
+
+    /**
+     * Uses [InputTypeTrace] to generates a trace
+     *
+     * @param input Parsed proto data
+     * @param from Initial timestamp to be parsed
+     * @param to Final timestamp to be parsed
+     * @param addInitialEntry If the last entry smaller than [from] should be included as well
+     * @param clearCache If the caching used while parsing the object should be cleared
+     */
+    fun parse(
+        input: InputTypeTrace,
+        from: Timestamp,
+        to: Timestamp,
+        addInitialEntry: Boolean = true,
+        clearCache: Boolean = true
+    ): OutputTypeTrace {
+        return CrossPlatform.log.withTracing("${this::class.simpleName}#parse") {
+            try {
+                doParse(input, from, to, addInitialEntry)
+            } finally {
+                if (clearCache) {
+                    Cache.clear()
+                }
+            }
+        }
+    }
+
+    /**
+     * Uses a [ByteArray] to generates a trace
+     *
+     * @param bytes Parsed proto data
+     * @param from Initial timestamp to be parsed
+     * @param to Final timestamp to be parsed
+     * @param addInitialEntry If the last entry smaller than [from] should be included as well
+     * @param clearCache If the caching used while parsing the object should be cleared
+     */
+    fun parse(
+        bytes: ByteArray,
+        from: Timestamp,
+        to: Timestamp,
+        addInitialEntry: Boolean = true,
+        clearCache: Boolean = true
+    ): OutputTypeTrace {
+        val input = decodeByteArray(bytes)
+        return parse(input, from, to, addInitialEntry, clearCache)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/parsers/events/EventLogParser.kt b/libraries/flicker/src/android/tools/common/parsers/events/EventLogParser.kt
new file mode 100644
index 0000000..4f9fe52
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/parsers/events/EventLogParser.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.parsers.events
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractParser
+import android.tools.common.traces.events.CujEvent
+import android.tools.common.traces.events.Event
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.events.EventLog.Companion.MAGIC_NUMBER
+import android.tools.common.traces.events.FocusEvent
+
+operator fun <T> List<T>.component6(): T = get(5)
+
+class EventLogParser : AbstractParser<Array<String>, EventLog>() {
+    override val traceName: String = "Event Log"
+
+    override fun doDecodeByteArray(bytes: ByteArray): Array<String> {
+        val logsString = bytes.decodeToString()
+        return logsString
+            .split("\n")
+            .dropWhile {
+                it.contains(MAGIC_NUMBER) || it.contains("beginning of events") || it.isBlank()
+            }
+            .dropLastWhile { it.isBlank() }
+            .toTypedArray()
+    }
+
+    override fun doParse(input: Array<String>): EventLog {
+        val events =
+            input.map { log ->
+                val (metaData, eventData) = log.split(":", limit = 2).map { it.trim() }
+                val (rawTimestamp, uid, pid, tid, priority, tag) = metaData.split("\\s+".toRegex())
+
+                val timestamp =
+                    CrossPlatform.timestamp.from(unixNanos = rawTimestamp.replace(".", "").toLong())
+                parseEvent(timestamp, pid.toInt(), uid, tid.toInt(), tag, eventData)
+            }
+
+        return EventLog(events.toTypedArray())
+    }
+
+    private fun parseEvent(
+        timestamp: Timestamp,
+        pid: Int,
+        uid: String,
+        tid: Int,
+        tag: String,
+        eventData: String
+    ): Event {
+        eventData.split(",")
+
+        return when (tag) {
+            INPUT_FOCUS_TAG -> {
+                FocusEvent(timestamp, pid, uid, tid, parseDataArray(eventData))
+            }
+            JANK_CUJ_BEGIN_TAG -> {
+                CujEvent(timestamp, pid, uid, tid, tag, eventData)
+            }
+            JANK_CUJ_END_TAG -> {
+                CujEvent(timestamp, pid, uid, tid, tag, eventData)
+            }
+            JANK_CUJ_CANCEL_TAG -> {
+                CujEvent(timestamp, pid, uid, tid, tag, eventData)
+            }
+            else -> {
+                Event(timestamp, pid, uid, tid, tag)
+            }
+        }
+    }
+
+    private fun parseDataArray(data: String): Array<String> {
+        require(data.first() == '[')
+        require(data.last() == ']')
+        return data.drop(1).dropLast(1).split(",").toTypedArray()
+    }
+
+    fun parse(bytes: ByteArray, from: Timestamp, to: Timestamp): EventLog {
+        require(from.unixNanos < to.unixNanos) { "'to' needs to be greater than 'from'" }
+        require(from.hasUnixTimestamp && to.hasUnixTimestamp) { "Missing required timestamp type" }
+        return doParse(
+            this.doDecodeByteArray(bytes)
+                .dropWhile { getTimestampFromRawEntry(it).unixNanos < from.unixNanos }
+                .dropLastWhile { getTimestampFromRawEntry(it).unixNanos > to.unixNanos }
+                .toTypedArray()
+        )
+    }
+
+    private fun getTimestampFromRawEntry(entry: String): Timestamp {
+        val (metaData, _) = entry.split(":", limit = 2).map { it.trim() }
+        val (rawTimestamp, _, _, _, _, _) = metaData.split("\\s+".toRegex())
+        return CrossPlatform.timestamp.from(unixNanos = rawTimestamp.replace(".", "").toLong())
+    }
+
+    companion object {
+        const val EVENT_LOG_INPUT_FOCUS_TAG = 62001
+
+        const val WM_JANK_CUJ_EVENTS_BEGIN_REQUEST = 37001
+        const val WM_JANK_CUJ_EVENTS_END_REQUEST = 37002
+        const val WM_JANK_CUJ_EVENTS_CANCEL_REQUEST = 37003
+
+        const val INPUT_FOCUS_TAG = "input_focus"
+        const val JANK_CUJ_BEGIN_TAG = "jank_cuj_events_begin_request"
+        const val JANK_CUJ_END_TAG = "jank_cuj_events_end_request"
+        const val JANK_CUJ_CANCEL_TAG = "jank_cuj_events_cancel_request"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/Condition.kt b/libraries/flicker/src/android/tools/common/traces/Condition.kt
new file mode 100644
index 0000000..501a86e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/Condition.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.traces
+
+import kotlin.js.JsName
+
+/**
+ * The utility class to wait a condition with customized options. The default retry policy is 5
+ * times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
+ * ```
+ *     println("Success");
+ * ```
+ * } // Wait for customized result with customized validation. String result =
+ * Condition.waitForResult(new Condition<String>("string comparison")
+ * ```
+ *         .setResultSupplier(() -> "Result string")
+ *         .setResultValidator(str -> str.equals("Expected string"))
+ *         .setRetryIntervalMs(500)
+ *         .setRetryLimit(3)
+ *         .setOnFailure(str -> println("Failed on " + str)));
+ * ```
+ * </pre>
+ *
+ * @param message The message to show what is waiting for.
+ * @param condition If it returns true, that means the condition is satisfied.
+ */
+open class Condition<T>(
+    @JsName("message") protected open val message: String = "",
+    @JsName("condition") protected open val condition: (T) -> Boolean
+) {
+    /** @return if [value] satisfies the condition */
+    @JsName("isSatisfied")
+    fun isSatisfied(value: T): Boolean {
+        return condition.invoke(value)
+    }
+
+    /** @return the negation of the current assertion */
+    @JsName("negate")
+    fun negate(): Condition<T> = Condition(message = "!$message") { !this.condition.invoke(it) }
+
+    /** @return a formatted message for the passing or failing condition on a state */
+    @JsName("getMessage")
+    open fun getMessage(value: T): String = "$message(passed=${isSatisfied(value)})"
+
+    override fun toString(): String = this.message
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/ConditionList.kt b/libraries/flicker/src/android/tools/common/traces/ConditionList.kt
new file mode 100644
index 0000000..eed900e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/ConditionList.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.traces
+
+import kotlin.js.JsName
+
+/**
+ * The utility class to validate a set of conditions
+ *
+ * This class is used to easily integrate multiple conditions into a single verification, for
+ * example, during [WaitCondition], while keeping the individual conditions separate for better
+ * reuse
+ *
+ * @param conditions conditions to be checked
+ */
+class ConditionList<T>(@JsName("conditions") val conditions: List<Condition<T>>) :
+    Condition<T>("", { false }) {
+    constructor(vararg conditions: Condition<T>) : this(listOf(*conditions))
+
+    override val message: String
+        get() {
+            return "(\n${
+                conditions
+                    .joinToString(" and \n") { it.toString() }
+                    .prependIndent("    ")
+            }\n)"
+        }
+
+    override val condition: (T) -> Boolean
+        get() = { conditions.all { condition -> condition.isSatisfied(it) } }
+
+    override fun getMessage(value: T): String {
+        return "(\n${
+            conditions
+                .joinToString(" and \n") { it.getMessage(value) }
+                .prependIndent("    ")
+        }\n)"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/ConditionsFactory.kt b/libraries/flicker/src/android/tools/common/traces/ConditionsFactory.kt
new file mode 100644
index 0000000..b4f9bc8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/ConditionsFactory.kt
@@ -0,0 +1,381 @@
+/*
+ * 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.traces
+
+import android.tools.common.PlatformConsts
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.Transform
+import android.tools.common.traces.surfaceflinger.Transform.Companion.isFlagSet
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowState
+import kotlin.js.JsName
+
+object ConditionsFactory {
+    private fun getNavBarComponent(wmState: WindowManagerState) =
+        if (wmState.isTablet) ComponentNameMatcher.TASK_BAR else ComponentNameMatcher.NAV_BAR
+
+    /**
+     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
+     * windows are visible
+     */
+    @JsName("isNavOrTaskBarVisible")
+    fun isNavOrTaskBarVisible(): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(
+                isNavOrTaskBarWindowVisible(),
+                isNavOrTaskBarLayerVisible(),
+                isNavOrTaskBarLayerOpaque()
+            )
+        )
+
+    /**
+     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
+     * windows are visible
+     */
+    @JsName("isNavOrTaskBarWindowVisible")
+    fun isNavOrTaskBarWindowVisible(): Condition<DeviceStateDump> =
+        Condition("isNavBarOrTaskBarWindowVisible") {
+            val component = getNavBarComponent(it.wmState)
+            it.wmState.isWindowSurfaceShown(component)
+        }
+
+    /**
+     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
+     * layers are visible
+     */
+    @JsName("isNavOrTaskBarLayerVisible")
+    fun isNavOrTaskBarLayerVisible(): Condition<DeviceStateDump> =
+        Condition("isNavBarOrTaskBarLayerVisible") {
+            val component = getNavBarComponent(it.wmState)
+            it.layerState.isVisible(component)
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
+    @JsName("isNavOrTaskBarLayerOpaque")
+    fun isNavOrTaskBarLayerOpaque(): Condition<DeviceStateDump> =
+        Condition("isNavOrTaskBarLayerOpaque") {
+            val component = getNavBarComponent(it.wmState)
+            it.layerState.getLayerWithBuffer(component)?.color?.isOpaque ?: false
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
+    @JsName("isNavBarVisible")
+    fun isNavBarVisible(): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(isNavBarWindowVisible(), isNavBarLayerVisible(), isNavBarLayerOpaque())
+        )
+
+    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
+    @JsName("isNavBarWindowVisible")
+    fun isNavBarWindowVisible(): Condition<DeviceStateDump> =
+        Condition("isNavBarWindowVisible") {
+            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.NAV_BAR)
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is visible */
+    @JsName("isNavBarLayerVisible")
+    fun isNavBarLayerVisible(): Condition<DeviceStateDump> =
+        isLayerVisible(ComponentNameMatcher.NAV_BAR)
+
+    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
+    @JsName("isNavBarLayerOpaque")
+    fun isNavBarLayerOpaque(): Condition<DeviceStateDump> =
+        Condition("isNavBarLayerOpaque") {
+            it.layerState.getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)?.color?.isOpaque ?: false
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
+    @JsName("isTaskBarVisible")
+    fun isTaskBarVisible(): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(isTaskBarWindowVisible(), isTaskBarLayerVisible(), isTaskBarLayerOpaque())
+        )
+
+    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
+    @JsName("isTaskBarWindowVisible")
+    fun isTaskBarWindowVisible(): Condition<DeviceStateDump> =
+        Condition("isTaskBarWindowVisible") {
+            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.TASK_BAR)
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is visible */
+    @JsName("isTaskBarLayerVisible")
+    fun isTaskBarLayerVisible(): Condition<DeviceStateDump> =
+        isLayerVisible(ComponentNameMatcher.TASK_BAR)
+
+    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is opaque */
+    @JsName("isTaskBarLayerOpaque")
+    fun isTaskBarLayerOpaque(): Condition<DeviceStateDump> =
+        Condition("isTaskBarLayerOpaque") {
+            it.layerState.getLayerWithBuffer(ComponentNameMatcher.TASK_BAR)?.color?.isOpaque
+                ?: false
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
+    @JsName("isStatusBarVisible")
+    fun isStatusBarVisible(): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(isStatusBarWindowVisible(), isStatusBarLayerVisible(), isStatusBarLayerOpaque())
+        )
+
+    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
+    @JsName("isStatusBarWindowVisible")
+    fun isStatusBarWindowVisible(): Condition<DeviceStateDump> =
+        Condition("isStatusBarWindowVisible") {
+            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.STATUS_BAR)
+        }
+
+    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is visible */
+    @JsName("isStatusBarLayerVisible")
+    fun isStatusBarLayerVisible(): Condition<DeviceStateDump> =
+        isLayerVisible(ComponentNameMatcher.STATUS_BAR)
+
+    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is opaque */
+    @JsName("isStatusBarLayerOpaque")
+    fun isStatusBarLayerOpaque(): Condition<DeviceStateDump> =
+        Condition("isStatusBarLayerOpaque") {
+            it.layerState.getLayerWithBuffer(ComponentNameMatcher.STATUS_BAR)?.color?.isOpaque
+                ?: false
+        }
+
+    @JsName("isHomeActivityVisible")
+    fun isHomeActivityVisible(): Condition<DeviceStateDump> =
+        Condition("isHomeActivityVisible") { it.wmState.isHomeActivityVisible }
+
+    @JsName("isRecentsActivityVisible")
+    fun isRecentsActivityVisible(): Condition<DeviceStateDump> =
+        Condition("isRecentsActivityVisible") {
+            it.wmState.isHomeActivityVisible || it.wmState.isRecentsActivityVisible
+        }
+
+    @JsName("isLauncherLayerVisible")
+    fun isLauncherLayerVisible(): Condition<DeviceStateDump> =
+        Condition("isLauncherLayerVisible") {
+            it.layerState.isVisible(ComponentNameMatcher.LAUNCHER) ||
+                it.layerState.isVisible(ComponentNameMatcher.AOSP_LAUNCHER)
+        }
+
+    /**
+     * Condition to check if WM app transition is idle
+     *
+     * Because in shell transitions, active recents animation is running transition (never idle)
+     * this method always assumed recents are idle
+     */
+    @JsName("isAppTransitionIdle")
+    fun isAppTransitionIdle(displayId: Int): Condition<DeviceStateDump> =
+        Condition("isAppTransitionIdle[$displayId]") {
+            (it.wmState.isHomeRecentsComponent && it.wmState.isHomeActivityVisible) ||
+                it.wmState.isRecentsActivityVisible ||
+                it.wmState.getDisplay(displayId)?.appTransitionState ==
+                    WindowManagerState.APP_STATE_IDLE
+        }
+
+    @JsName("containsActivity")
+    fun containsActivity(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("containsActivity[${componentMatcher.toActivityIdentifier()}]") {
+            it.wmState.containsActivity(componentMatcher)
+        }
+
+    @JsName("containsWindow")
+    fun containsWindow(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("containsWindow[${componentMatcher.toWindowIdentifier()}]") {
+            it.wmState.containsWindow(componentMatcher)
+        }
+
+    @JsName("isWindowSurfaceShown")
+    fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("isWindowSurfaceShown[${componentMatcher.toWindowIdentifier()}]") {
+            it.wmState.isWindowSurfaceShown(componentMatcher)
+        }
+
+    @JsName("isActivityVisible")
+    fun isActivityVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("isActivityVisible[${componentMatcher.toActivityIdentifier()}]") {
+            it.wmState.isActivityVisible(componentMatcher)
+        }
+
+    @JsName("isWMStateComplete")
+    fun isWMStateComplete(): Condition<DeviceStateDump> =
+        Condition("isWMStateComplete") { it.wmState.isComplete() }
+
+    @JsName("hasRotation")
+    fun hasRotation(expectedRotation: Rotation, displayId: Int): Condition<DeviceStateDump> {
+        val hasRotationCondition =
+            Condition<DeviceStateDump>("hasRotation[$expectedRotation, display=$displayId]") {
+                val currRotation = it.wmState.getRotation(displayId)
+                currRotation == expectedRotation
+            }
+        return ConditionList(
+            listOf(
+                hasRotationCondition,
+                isLayerVisible(ComponentNameMatcher.ROTATION).negate(),
+                isLayerVisible(ComponentNameMatcher.BACK_SURFACE).negate(),
+                hasLayersAnimating().negate()
+            )
+        )
+    }
+
+    @JsName("isWindowVisible")
+    fun isWindowVisible(
+        componentMatcher: IComponentMatcher,
+        displayId: Int = 0
+    ): Condition<DeviceStateDump> =
+        ConditionList(
+            containsActivity(componentMatcher),
+            containsWindow(componentMatcher),
+            isActivityVisible(componentMatcher),
+            isWindowSurfaceShown(componentMatcher),
+            isAppTransitionIdle(displayId)
+        )
+
+    @JsName("isLayerVisible")
+    fun isLayerVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("isLayerVisible[${componentMatcher.toLayerIdentifier()}]") {
+            it.layerState.isVisible(componentMatcher)
+        }
+
+    @JsName("isLayerVisibleForLayerId")
+    fun isLayerVisible(layerId: Int): Condition<DeviceStateDump> =
+        Condition("isLayerVisible[layerId=$layerId]") {
+            it.layerState.getLayerById(layerId)?.isVisible ?: false
+        }
+
+    @JsName("isLayerColorAlphaOne")
+    fun isLayerColorAlphaOne(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
+        Condition("isLayerColorAlphaOne[${componentMatcher.toLayerIdentifier()}]") {
+            it.layerState.visibleLayers
+                .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
+                .any { layer -> layer.color.isOpaque }
+        }
+
+    @JsName("isLayerColorAlphaOneForLayerId")
+    fun isLayerColorAlphaOne(layerId: Int): Condition<DeviceStateDump> =
+        Condition("isLayerColorAlphaOne[$layerId]") {
+            val layer = it.layerState.getLayerById(layerId)
+            layer?.color?.a == 1.0f
+        }
+
+    @JsName("isLayerTransformFlagSet")
+    fun isLayerTransformFlagSet(
+        componentMatcher: IComponentMatcher,
+        transform: Int
+    ): Condition<DeviceStateDump> =
+        Condition(
+            "isLayerTransformFlagSet[" +
+                "${componentMatcher.toLayerIdentifier()}," +
+                "transform=$transform]"
+        ) {
+            it.layerState.visibleLayers
+                .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
+                .any { layer -> isTransformFlagSet(layer, transform) }
+        }
+
+    @JsName("isLayerTransformFlagSetForLayerId")
+    fun isLayerTransformFlagSet(layerId: Int, transform: Int): Condition<DeviceStateDump> =
+        Condition("isLayerTransformFlagSet[$layerId, $transform]") {
+            val layer = it.layerState.getLayerById(layerId)
+            layer?.transform?.type?.isFlagSet(transform) ?: false
+        }
+
+    @JsName("isLayerTransformIdentity")
+    fun isLayerTransformIdentity(layerId: Int): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(
+                isLayerTransformFlagSet(layerId, Transform.SCALE_VAL).negate(),
+                isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL).negate(),
+                isLayerTransformFlagSet(layerId, Transform.ROTATE_VAL).negate()
+            )
+        )
+
+    @JsName("isTransformFlagSet")
+    private fun isTransformFlagSet(layer: Layer, transform: Int): Boolean =
+        layer.transform.type?.isFlagSet(transform) ?: false
+
+    @JsName("hasLayersAnimating")
+    fun hasLayersAnimating(): Condition<DeviceStateDump> {
+        var prevState: DeviceStateDump? = null
+        return ConditionList(
+            Condition("hasLayersAnimating") {
+                val result = it.layerState.isAnimating(prevState?.layerState)
+                prevState = it
+                result
+            },
+            isLayerVisible(ComponentNameMatcher.SNAPSHOT).negate(),
+            isLayerVisible(ComponentNameMatcher.SPLASH_SCREEN).negate()
+        )
+    }
+
+    @JsName("isPipWindowLayerSizeMatch")
+    fun isPipWindowLayerSizeMatch(layerId: Int): Condition<DeviceStateDump> =
+        Condition("isPipWindowLayerSizeMatch[layerId=$layerId]") {
+            val pipWindow =
+                it.wmState.pinnedWindows.firstOrNull { pinnedWindow ->
+                    pinnedWindow.layerId == layerId
+                }
+                    ?: error("Unable to find window with layerId $layerId")
+            val windowHeight = pipWindow.frame.height.toFloat()
+            val windowWidth = pipWindow.frame.width.toFloat()
+
+            val pipLayer = it.layerState.getLayerById(layerId)
+            val layerHeight =
+                pipLayer?.sourceBounds?.height ?: error("Unable to find layer with id $layerId")
+            val layerWidth = pipLayer.sourceBounds.width
+
+            windowHeight == layerHeight && windowWidth == layerWidth
+        }
+
+    @JsName("hasPipWindow")
+    fun hasPipWindow(): Condition<DeviceStateDump> =
+        Condition("hasPipWindow") { it.wmState.hasPipWindow() }
+
+    @JsName("isImeShown")
+    fun isImeShown(displayId: Int): Condition<DeviceStateDump> =
+        ConditionList(
+            listOf(
+                isImeOnDisplay(displayId),
+                isLayerVisible(ComponentNameMatcher.IME),
+                isImeSurfaceShown(),
+                isWindowSurfaceShown(ComponentNameMatcher.IME)
+            )
+        )
+
+    @JsName("isImeOnDisplay")
+    private fun isImeOnDisplay(displayId: Int): Condition<DeviceStateDump> =
+        Condition("isImeOnDisplay[$displayId]") {
+            it.wmState.inputMethodWindowState?.displayId == displayId
+        }
+
+    @JsName("isImeSurfaceShown")
+    private fun isImeSurfaceShown(): Condition<DeviceStateDump> =
+        Condition("isImeSurfaceShown") { it.wmState.inputMethodWindowState?.isSurfaceShown == true }
+
+    @JsName("isAppLaunchEnded")
+    fun isAppLaunchEnded(taskId: Int): Condition<DeviceStateDump> =
+        Condition("containsVisibleAppLaunchWindow[taskId=$taskId]") { dump ->
+            val windowStates =
+                dump.wmState.getRootTask(taskId)?.activities?.flatMap {
+                    it.children.filterIsInstance<WindowState>()
+                }
+            windowStates != null &&
+                windowStates.none {
+                    it.attributes.type == PlatformConsts.TYPE_APPLICATION_STARTING && it.isVisible
+                }
+        }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/DeviceStateDump.kt b/libraries/flicker/src/android/tools/common/traces/DeviceStateDump.kt
new file mode 100644
index 0000000..1cfc160
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/DeviceStateDump.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.traces
+
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.wm.WindowManagerState
+
+/**
+ * Represents a state dump containing the [WindowManagerState] and the [LayerTraceEntry] both
+ * parsed.
+ */
+class DeviceStateDump(
+    override val wmState: WindowManagerState,
+    override val layerState: LayerTraceEntry
+) : NullableDeviceStateDump(wmState, layerState)
diff --git a/libraries/flicker/src/android/tools/common/traces/DeviceTraceDump.kt b/libraries/flicker/src/android/tools/common/traces/DeviceTraceDump.kt
new file mode 100644
index 0000000..cb5624b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/DeviceTraceDump.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.traces
+
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import kotlin.js.JsName
+
+/**
+ * Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed and
+ * in raw (byte) data.
+ *
+ * @param wmTrace Parsed [WindowManagerTrace]
+ * @param layersTrace Parsed [LayersTrace]
+ * @param transactionsTrace Parse [TransactionsTrace]
+ * @param transitionsTrace Parsed [TransitionsTrace]
+ * @param eventLog Parsed [EventLog]
+ */
+class DeviceTraceDump(
+    @JsName("wmTrace") val wmTrace: WindowManagerTrace?,
+    @JsName("layersTrace") val layersTrace: LayersTrace?,
+    @JsName("transactionsTrace") val transactionsTrace: TransactionsTrace? = null,
+    @JsName("transitionsTrace") val transitionsTrace: TransitionsTrace? = null,
+    @JsName("eventLog") val eventLog: EventLog? = null,
+) {
+    /** A deviceTraceDump is considered valid if at least one of the layers/wm traces is non-null */
+    val isValid: Boolean
+        get() {
+            if (wmTrace == null && layersTrace == null) {
+                return false
+            }
+            return true
+        }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/NullableDeviceStateDump.kt b/libraries/flicker/src/android/tools/common/traces/NullableDeviceStateDump.kt
new file mode 100644
index 0000000..abeffcc
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/NullableDeviceStateDump.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.traces
+
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.wm.WindowManagerState
+import kotlin.js.JsName
+
+/**
+ * Represents a state dump optionally containing the [WindowManagerState] and the [LayerTraceEntry]
+ * parsed.
+ */
+open class NullableDeviceStateDump(
+    /** Parsed [WindowManagerState] */
+    @JsName("wmState") open val wmState: WindowManagerState?,
+
+    /** Parsed [LayerTraceEntry] */
+    @JsName("layerState") open val layerState: LayerTraceEntry?
+)
diff --git a/libraries/flicker/src/android/tools/common/traces/WaitCondition.kt b/libraries/flicker/src/android/tools/common/traces/WaitCondition.kt
new file mode 100644
index 0000000..8598a4b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/WaitCondition.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.traces
+
+import kotlin.js.JsName
+
+/**
+ * The utility class to wait a condition with customized options. The default retry policy is 5
+ * times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
+ * ```
+ *     println("Success");
+ * ```
+ * } // Wait for customized result with customized validation. String result =
+ * WaitForCondition.Builder(supplier = () -> "Result string")
+ * ```
+ *         .withCondition(str -> str.equals("Expected string"))
+ *         .withRetryIntervalMs(500)
+ *         .withRetryLimit(3)
+ *         .onFailure(str -> println("Failed on " + str)))
+ *         .build()
+ *         .waitFor()
+ * ```
+ * </pre>
+ *
+ * @param condition If it returns true, that means the condition is satisfied.
+ */
+class WaitCondition<T>
+private constructor(
+    @JsName("supplier") private val supplier: () -> T,
+    @JsName("condition") private val condition: Condition<T>,
+    @JsName("retryLimit") private val retryLimit: Int,
+    @JsName("onLog") private val onLog: ((String, Boolean) -> Unit)?,
+    @JsName("onFailure") private val onFailure: ((T) -> Any)?,
+    @JsName("onRetry") private val onRetry: ((T) -> Any)?,
+    @JsName("onSuccess") private val onSuccess: ((T) -> Any)?,
+    @JsName("onStart") private val onStart: ((String) -> Any)?,
+    @JsName("onEnd") private val onEnd: (() -> Any)?
+) {
+    /** @return `false` if the condition does not satisfy within the time limit. */
+    @JsName("waitFor")
+    fun waitFor(): Boolean {
+        onStart?.invoke("waitFor")
+        try {
+            return doWaitFor()
+        } finally {
+            onEnd?.invoke()
+        }
+    }
+
+    private fun doWaitFor(): Boolean {
+        onLog?.invoke("***Waiting for $condition", /* isError */ false)
+        var currState: T? = null
+        var success = false
+        for (i in 0..retryLimit) {
+            val result = doWaitForRetry(i)
+            success = result.first
+            currState = result.second
+            if (success) {
+                break
+            } else if (i < retryLimit) {
+                onRetry?.invoke(currState)
+            }
+        }
+
+        return if (success) {
+            true
+        } else {
+            doNotifyFailure(currState)
+            false
+        }
+    }
+
+    private fun doWaitForRetry(retryNr: Int): Pair<Boolean, T> {
+        onStart?.invoke("doWaitForRetry")
+        try {
+            val currState = supplier.invoke()
+            return if (condition.isSatisfied(currState)) {
+                onLog?.invoke("***Waiting for $condition ... Success!", /* isError */ false)
+                onSuccess?.invoke(currState)
+                Pair(true, currState)
+            } else {
+                val detailedMessage = condition.getMessage(currState)
+                onLog?.invoke(
+                    "***Waiting for $detailedMessage... retry=${retryNr + 1}",
+                    /* isError */ true
+                )
+                Pair(false, currState)
+            }
+        } finally {
+            onEnd?.invoke()
+        }
+    }
+
+    private fun doNotifyFailure(currState: T?) {
+        val detailedMessage =
+            if (currState != null) {
+                condition.getMessage(currState)
+            } else {
+                condition.toString()
+            }
+        onLog?.invoke("***Waiting for $detailedMessage ... Failed!", /* isError */ true)
+        if (onFailure != null) {
+            require(currState != null) { "Missing last result for failure notification" }
+            onFailure.invoke(currState)
+        }
+    }
+
+    class Builder<T>(
+        @JsName("supplier") private val supplier: () -> T,
+        @JsName("retryLimit") private var retryLimit: Int
+    ) {
+        @JsName("conditions") private val conditions = mutableListOf<Condition<T>>()
+        private var onStart: ((String) -> Any)? = null
+        private var onEnd: (() -> Any)? = null
+        private var onFailure: ((T) -> Any)? = null
+        private var onRetry: ((T) -> Any)? = null
+        private var onSuccess: ((T) -> Any)? = null
+        private var onLog: ((String, Boolean) -> Unit)? = null
+
+        @JsName("withCondition")
+        fun withCondition(condition: Condition<T>) = apply { conditions.add(condition) }
+
+        @JsName("withConditionAndMessage")
+        fun withCondition(message: String, condition: (T) -> Boolean) = apply {
+            withCondition(Condition(message, condition))
+        }
+
+        @JsName("spreadConditionList")
+        private fun spreadConditionList(): List<Condition<T>> =
+            conditions.flatMap {
+                if (it is ConditionList<T>) {
+                    it.conditions
+                } else {
+                    listOf(it)
+                }
+            }
+
+        /**
+         * Executes the action when the condition does not satisfy within the time limit. The passed
+         * object to the consumer will be the last result from the supplier.
+         */
+        @JsName("onFailure")
+        fun onFailure(onFailure: (T) -> Any): Builder<T> = apply { this.onFailure = onFailure }
+
+        @JsName("onLog")
+        fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> = apply { this.onLog = onLog }
+
+        @JsName("onRetry")
+        fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = apply { this.onRetry = onRetry }
+
+        @JsName("onStart")
+        fun onStart(onStart: ((String) -> Any)? = null): Builder<T> = apply {
+            this.onStart = onStart
+        }
+
+        @JsName("onEnd")
+        fun onEnd(onEnd: (() -> Any)? = null): Builder<T> = apply { this.onEnd = onEnd }
+
+        @JsName("onSuccess")
+        fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = apply {
+            this.onSuccess = onRetry
+        }
+
+        @JsName("build")
+        fun build(): WaitCondition<T> =
+            WaitCondition(
+                supplier,
+                ConditionList(spreadConditionList()),
+                retryLimit,
+                onLog,
+                onFailure,
+                onRetry,
+                onSuccess,
+                onStart,
+                onEnd
+            )
+    }
+
+    companion object {
+        // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
+        // constant time, currently keep the default as 5*1s because most of the original code
+        // uses it, and some tests might be sensitive to the waiting interval.
+        @JsName("DEFAULT_RETRY_LIMIT") const val DEFAULT_RETRY_LIMIT = 50
+        @JsName("DEFAULT_RETRY_INTERVAL_MS") const val DEFAULT_RETRY_INTERVAL_MS = 100L
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/Cuj.kt b/libraries/flicker/src/android/tools/common/traces/events/Cuj.kt
new file mode 100644
index 0000000..1780660
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/Cuj.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+
+data class Cuj(
+    val cuj: CujType,
+    val startTimestamp: Timestamp,
+    val endTimestamp: Timestamp,
+    val canceled: Boolean
+) : ITraceEntry {
+    override val timestamp: Timestamp = startTimestamp
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/CujEvent.kt b/libraries/flicker/src/android/tools/common/traces/events/CujEvent.kt
new file mode 100644
index 0000000..2838e98
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/CujEvent.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+
+/**
+ * Represents a CUJ Event from the [EventLog]
+ *
+ * {@inheritDoc}
+ */
+class CujEvent(
+    timestamp: Timestamp,
+    val cuj: CujType,
+    processId: Int,
+    uid: String,
+    threadId: Int,
+    tag: String
+) : Event(timestamp, processId, uid, threadId, tag) {
+
+    val type: Type =
+        when (tag) {
+            JANK_CUJ_BEGIN_TAG -> Type.START
+            JANK_CUJ_END_TAG -> Type.END
+            JANK_CUJ_CANCEL_TAG -> Type.CANCEL
+            else -> error("Unhandled tag type")
+        }
+
+    constructor(
+        timestamp: Timestamp,
+        processId: Int,
+        uid: String,
+        threadId: Int,
+        tag: String,
+        data: String
+    ) : this(
+        CrossPlatform.timestamp.from(
+            elapsedNanos = getElapsedTimestampFromData(data),
+            systemUptimeNanos = getSystemUptimeNanosFromData(data),
+            unixNanos = timestamp.unixNanos
+        ),
+        getCujMarkerFromData(data),
+        processId,
+        uid,
+        threadId,
+        tag
+    )
+
+    override fun toString(): String {
+        return "CujEvent(" +
+            "timestamp=$timestamp, " +
+            "cuj=$cuj, " +
+            "processId=$processId, " +
+            "uid=$uid, " +
+            "threadId=$threadId, " +
+            "tag=$tag" +
+            ")"
+    }
+
+    companion object {
+        private fun getCujMarkerFromData(data: String): CujType {
+            val dataEntries = getDataEntries(data)
+            val eventId = dataEntries[0].toInt()
+            return CujType.from(eventId)
+        }
+
+        private fun getElapsedTimestampFromData(data: String): Long {
+            val dataEntries = getDataEntries(data)
+            return dataEntries[1].toLong()
+        }
+
+        private fun getSystemUptimeNanosFromData(data: String): Long {
+            val dataEntries = getDataEntries(data)
+            return dataEntries[2].toLong()
+        }
+
+        private fun getDataEntries(data: String): List<String> {
+            require("""\[\d+,\d+,\d+]""".toRegex().matches(data)) {
+                "Data ($data) didn't match expected format"
+            }
+
+            return data.slice(1..data.length - 2).split(",")
+        }
+
+        enum class Type {
+            START,
+            END,
+            CANCEL
+        }
+
+        const val JANK_CUJ_BEGIN_TAG = "jank_cuj_events_begin_request"
+        const val JANK_CUJ_END_TAG = "jank_cuj_events_end_request"
+        const val JANK_CUJ_CANCEL_TAG = "jank_cuj_events_cancel_request"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/CujTrace.kt b/libraries/flicker/src/android/tools/common/traces/events/CujTrace.kt
new file mode 100644
index 0000000..cd89079
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/CujTrace.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.CrossPlatform
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+
+class CujTrace(override val entries: Array<Cuj>) : ITrace<Cuj> {
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): CujTrace {
+        return CujTrace(
+            entries
+                .dropWhile { it.endTimestamp < startTimestamp }
+                .dropLastWhile { it.startTimestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+
+    companion object {
+        fun from(cujEvents: Array<CujEvent>): CujTrace {
+            val cujs = mutableListOf<Cuj>()
+
+            val sortedCujEvents = cujEvents.sortedBy { it.timestamp.unixNanos }
+            val startEvents = sortedCujEvents.filter { it.type == CujEvent.Companion.Type.START }
+            val endEvents = sortedCujEvents.filter { it.type == CujEvent.Companion.Type.END }
+            val canceledEvents =
+                sortedCujEvents.filter { it.type == CujEvent.Companion.Type.CANCEL }
+
+            for (startEvent in startEvents) {
+                val matchingEndEvent =
+                    endEvents.firstOrNull {
+                        it.cuj == startEvent.cuj && it.timestamp >= startEvent.timestamp
+                    }
+                val matchingCancelEvent =
+                    canceledEvents.firstOrNull {
+                        it.cuj == startEvent.cuj && it.timestamp >= startEvent.timestamp
+                    }
+
+                if (matchingCancelEvent == null && matchingEndEvent == null) {
+                    // CUJ started but not ended within the trace
+                    continue
+                }
+
+                val closingEvent =
+                    listOf(matchingCancelEvent, matchingEndEvent).minBy {
+                        it?.timestamp ?: CrossPlatform.timestamp.max()
+                    }
+                        ?: error("Should have found one matching closing event")
+                val canceled = closingEvent.type == CujEvent.Companion.Type.CANCEL
+
+                cujs.add(
+                    Cuj(startEvent.cuj, startEvent.timestamp, closingEvent.timestamp, canceled)
+                )
+            }
+
+            return CujTrace(cujs.toTypedArray())
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/CujType.kt b/libraries/flicker/src/android/tools/common/traces/events/CujType.kt
new file mode 100644
index 0000000..2983925
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/CujType.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.traces.events
+
+/**
+ * From com.android.internal.jank.InteractionJankMonitor.
+ *
+ * NOTE: Make sure order is the same as in {@see com.android.internal.jank.InteractionJankMonitor}.
+ */
+enum class CujType {
+    CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+    CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
+    CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
+    CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
+    CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
+    CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+    CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+    CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+    CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+    CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+    CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+    CUJ_LAUNCHER_QUICK_SWITCH,
+    CUJ_NOTIFICATION_HEADS_UP_APPEAR,
+    CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+    CUJ_NOTIFICATION_ADD,
+    CUJ_NOTIFICATION_REMOVE,
+    CUJ_NOTIFICATION_APP_START,
+    CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+    CUJ_LOCKSCREEN_PATTERN_APPEAR,
+    CUJ_LOCKSCREEN_PIN_APPEAR,
+    CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+    CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+    CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+    CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
+    CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
+    CUJ_LAUNCHER_OPEN_ALL_APPS,
+    CUJ_LAUNCHER_ALL_APPS_SCROLL,
+    CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
+    CUJ_SETTINGS_PAGE_SCROLL,
+    CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+    CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+    CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+    CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+    CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+    CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+    CUJ_PIP_TRANSITION,
+    CUJ_WALLPAPER_TRANSITION,
+    CUJ_USER_SWITCH,
+    CUJ_SPLASHSCREEN_AVD,
+    CUJ_SPLASHSCREEN_EXIT_ANIM,
+    CUJ_SCREEN_OFF,
+    CUJ_SCREEN_OFF_SHOW_AOD,
+    CUJ_ONE_HANDED_ENTER_TRANSITION,
+    CUJ_ONE_HANDED_EXIT_TRANSITION,
+    CUJ_UNFOLD_ANIM,
+    CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+    CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+    CUJ_SUW_LOADING_TO_NEXT_FLOW,
+    CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
+    CUJ_SPLIT_SCREEN_ENTER,
+    CUJ_SPLIT_SCREEN_EXIT,
+    CUJ_LOCKSCREEN_LAUNCH_CAMERA,
+    CUJ_SPLIT_SCREEN_RESIZE,
+    CUJ_SETTINGS_SLIDER,
+    CUJ_TAKE_SCREENSHOT,
+    CUJ_VOLUME_CONTROL,
+    CUJ_BIOMETRIC_PROMPT_TRANSITION,
+    CUJ_SETTINGS_TOGGLE,
+    CUJ_SHADE_DIALOG_OPEN,
+    CUJ_USER_DIALOG_OPEN,
+    CUJ_TASKBAR_EXPAND,
+    CUJ_TASKBAR_COLLAPSE,
+    CUJ_SHADE_CLEAR_ALL,
+    CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+    CUJ_LOCKSCREEN_OCCLUSION,
+    CUJ_RECENTS_SCROLLING,
+    CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+    CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
+    CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+
+    // KEEP AS LAST TYPE
+    // used to handle new types that haven't been added here yet but might be dumped by the platform
+    UNKNOWN;
+
+    companion object {
+        fun from(eventId: Int): CujType {
+            // -1 to account for unknown event type
+            if (eventId >= values().size - 1) {
+                return UNKNOWN
+            }
+            return values()[eventId]
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/Event.kt b/libraries/flicker/src/android/tools/common/traces/events/Event.kt
new file mode 100644
index 0000000..7c7b272
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/Event.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+
+/**
+ * Represents an Event from the [EventLog]
+ *
+ * @param timestamp The wall clock time in nanoseconds when the entry was written.
+ * @param processId The process ID which wrote the log entry
+ * @param uid The UID which wrote the log entry, special UIDs are strings instead of numbers (e.g.
+ * root)
+ * @param threadId The thread which wrote the log entry
+ * @param tag The type tag code of the entry
+ */
+open class Event(
+    override val timestamp: Timestamp,
+    val processId: Int,
+    val uid: String,
+    val threadId: Int,
+    val tag: String
+) : ITraceEntry
diff --git a/libraries/flicker/src/android/tools/common/traces/events/EventLog.kt b/libraries/flicker/src/android/tools/common/traces/events/EventLog.kt
new file mode 100644
index 0000000..c49650d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/EventLog.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import kotlin.js.JsName
+
+/**
+ * Represents the data from the Android EventLog and contains a collection of parsed events of
+ * interest.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+class EventLog(override val entries: Array<Event>) : ITrace<Event> {
+    @JsName("focusEvents")
+    val focusEvents: Array<FocusEvent> =
+        entries
+            .filterIsInstance<FocusEvent>()
+            .filter { it.type !== FocusEvent.Type.REQUESTED }
+            .toTypedArray()
+
+    @JsName("cujEvents")
+    val cujEvents: Array<CujEvent> = entries.filterIsInstance<CujEvent>().toTypedArray()
+
+    @JsName("cujTrace") val cujTrace: CujTrace = CujTrace.from(cujEvents)
+
+    companion object {
+        const val MAGIC_NUMBER = "EventLog"
+    }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): EventLog {
+        return EventLog(
+            entries
+                .dropWhile { it.timestamp < startTimestamp }
+                .dropLastWhile { it.timestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/events/FocusEvent.kt b/libraries/flicker/src/android/tools/common/traces/events/FocusEvent.kt
new file mode 100644
index 0000000..d570157
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/events/FocusEvent.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.traces.events
+
+import android.tools.common.Timestamp
+
+/**
+ * An Event from the [EventLog] representing a window focus change or request.
+ *
+ * @param timestamp The wall clock time in nanoseconds when the entry was written.
+ * @param window The window that is the target of the focus event.
+ * @param type The type of focus event this is.
+ * @param reason The reason for the focus event.
+ * @param processId The process ID which wrote the log entry
+ * @param uid The UID which wrote the log entry, special UIDs are strings instead of numbers (e.g.
+ * root)
+ * @param threadId The thread which wrote the log entry
+ * @param tag The type tag code of the entry
+ */
+class FocusEvent(
+    timestamp: Timestamp,
+    val window: String,
+    val type: Type,
+    val reason: String,
+    processId: Int,
+    uid: String,
+    threadId: Int
+) : Event(timestamp, processId, uid, threadId, INPUT_FOCUS_TAG) {
+    enum class Type {
+        GAINED,
+        LOST,
+        REQUESTED
+    }
+
+    constructor(
+        timestamp: Timestamp,
+        processId: Int,
+        uid: String,
+        threadId: Int,
+        data: Array<String>
+    ) : this(
+        timestamp,
+        getWindowFromData(data[0]),
+        getFocusFromData(data[0]),
+        data[1].removePrefix("reason="),
+        processId,
+        uid,
+        threadId
+    )
+
+    override fun toString(): String {
+        return "$timestamp: Focus ${type.name} $window Reason=$reason"
+    }
+
+    fun hasFocus(): Boolean {
+        return this.type == Type.GAINED
+    }
+
+    companion object {
+        private fun getWindowFromData(data: String): String {
+            var expectedWhiteSpace = 2
+            return data.dropWhile { !it.isWhitespace() || --expectedWhiteSpace > 0 }.drop(1)
+        }
+
+        private fun getFocusFromData(data: String): Type {
+            return when {
+                data.contains(" entering ") -> Type.GAINED
+                data.contains(" leaving ") -> Type.LOST
+                else -> Type.REQUESTED
+            }
+        }
+
+        const val INPUT_FOCUS_TAG = "input_focus"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/region/RegionEntry.kt b/libraries/flicker/src/android/tools/common/traces/region/RegionEntry.kt
new file mode 100644
index 0000000..72fd2d8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/region/RegionEntry.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.traces.region
+
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+import android.tools.common.datatypes.Region
+import kotlin.js.JsName
+
+/**
+ * Represents a single Region trace entry.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ *
+ * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
+ */
+class RegionEntry(@JsName("region") val region: Region, override val timestamp: Timestamp) :
+    ITraceEntry
diff --git a/libraries/flicker/src/android/tools/common/traces/region/RegionTrace.kt b/libraries/flicker/src/android/tools/common/traces/region/RegionTrace.kt
new file mode 100644
index 0000000..fbce435
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/region/RegionTrace.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.traces.region
+
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsName
+
+/**
+ * Contains a collection of parsed Region trace entries.
+ *
+ * Each entry is parsed into a list of [RegionEntry] objects.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+data class RegionTrace(
+    @JsName("components") val components: IComponentMatcher?,
+    override val entries: Array<RegionEntry>
+) : ITrace<RegionEntry> {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RegionTrace) return false
+
+        if (components != other.components) return false
+        if (!entries.contentEquals(other.entries)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = components.hashCode()
+        result = 31 * result + entries.contentHashCode()
+        return result
+    }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ITrace<RegionEntry> {
+        return RegionTrace(
+            components,
+            entries
+                .dropWhile { it.timestamp < startTimestamp }
+                .dropLastWhile { it.timestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Display.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Display.kt
new file mode 100644
index 0000000..d1009e0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Display.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Size
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/** Wrapper for DisplayProto (frameworks/native/services/surfaceflinger/layerproto/display.proto) */
+@JsExport
+class Display
+private constructor(
+    val id: ULong,
+    @JsName("name") val name: String,
+    @JsName("layerStackId") val layerStackId: Int,
+    @JsName("size") val size: Size,
+    @JsName("layerStackSpace") val layerStackSpace: Rect,
+    @JsName("transform") val transform: Transform,
+    @JsName("isVirtual") val isVirtual: Boolean
+) {
+    @JsName("idStr")
+    val idStr: String
+        get() = id.toString()
+    @JsName("isOff") val isOff = layerStackId == BLANK_LAYER_STACK
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Display) return false
+
+        if (id != other.id) return false
+        if (name != other.name) return false
+        if (layerStackId != other.layerStackId) return false
+        if (size != other.size) return false
+        if (layerStackSpace != other.layerStackSpace) return false
+        if (transform != other.transform) return false
+        if (isVirtual != other.isVirtual) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = id.toInt()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + layerStackId
+        result = 31 * result + size.hashCode()
+        result = 31 * result + layerStackSpace.hashCode()
+        result = 31 * result + transform.hashCode()
+        result = 31 * result + isVirtual.hashCode()
+        return result
+    }
+
+    companion object {
+        private const val BLANK_LAYER_STACK = -1
+
+        @JsName("EMPTY")
+        val EMPTY: Display
+            get() = withCache {
+                Display(
+                    id = 0.toULong(),
+                    name = "EMPTY",
+                    layerStackId = BLANK_LAYER_STACK,
+                    size = Size.EMPTY,
+                    layerStackSpace = Rect.EMPTY,
+                    transform = Transform.EMPTY,
+                    isVirtual = false
+                )
+            }
+
+        @JsName("from")
+        fun from(
+            id: ULong,
+            name: String,
+            layerStackId: Int,
+            size: Size,
+            layerStackSpace: Rect,
+            transform: Transform,
+            isVirtual: Boolean
+        ): Display = withCache {
+            Display(id, name, layerStackId, size, layerStackSpace, transform, isVirtual)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Flag.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Flag.kt
new file mode 100644
index 0000000..167a571
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Flag.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.traces.surfaceflinger
+
+import kotlin.js.JsExport
+
+/** Layer state flags as defined in LayerState.h */
+@JsExport
+enum class Flag(val value: Int) {
+    HIDDEN(0x01),
+    OPAQUE(0x02),
+    SKIP_SCREENSHOT(0x40),
+    SECURE(0x80),
+    ENABLE_BACKPRESSURE(0x100),
+    DISPLAY_DECORATION(0x200),
+    IGNORE_DESTINATION_FRAME(0x400)
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/HwcCompositionType.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/HwcCompositionType.kt
new file mode 100644
index 0000000..81f4a6e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/HwcCompositionType.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.traces.surfaceflinger
+
+import kotlin.js.JsExport
+
+@JsExport
+enum class HwcCompositionType(val value: Int) {
+    INVALID(0),
+    CLIENT(1),
+    DEVICE(2),
+    SOLID_COLOR(3),
+    CURSOR(4),
+    SIDEBAND(5),
+    UNRECOGNIZED(-1)
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/ILayerProperties.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/ILayerProperties.kt
new file mode 100644
index 0000000..e02322c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/ILayerProperties.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Common properties of a layer that are not related to their position in the hierarchy
+ *
+ * These properties are frequently stable throughout the trace and can be more efficiently cached
+ * than the full layers
+ */
+@JsExport
+interface ILayerProperties {
+    val visibleRegion: Region?
+    @JsName("activeBuffer") val activeBuffer: ActiveBuffer
+    @JsName("flags") val flags: Int
+    @JsName("bounds") val bounds: RectF
+    @JsName("color") val color: Color
+    @JsName("isOpaque") val isOpaque: Boolean
+    @JsName("shadowRadius") val shadowRadius: Float
+    @JsName("cornerRadius") val cornerRadius: Float
+    @JsName("type") val type: String
+    @JsName("screenBounds") val screenBounds: RectF
+    @JsName("transform") val transform: Transform
+    @JsName("sourceBounds") val sourceBounds: RectF
+    @JsName("effectiveScalingMode") val effectiveScalingMode: Int
+    @JsName("bufferTransform") val bufferTransform: Transform
+    @JsName("hwcCompositionType") val hwcCompositionType: HwcCompositionType
+    @JsName("hwcCrop") val hwcCrop: RectF
+    @JsName("hwcFrame") val hwcFrame: Rect
+    @JsName("backgroundBlurRadius") val backgroundBlurRadius: Int
+    @JsName("crop") val crop: Rect
+    @JsName("isRelativeOf") val isRelativeOf: Boolean
+    @JsName("zOrderRelativeOfId") val zOrderRelativeOfId: Int
+    @JsName("stackId") val stackId: Int
+    @JsName("requestedTransform") val requestedTransform: Transform
+    @JsName("requestedColor") val requestedColor: Color
+    @JsName("cornerRadiusCrop") val cornerRadiusCrop: RectF
+    @JsName("inputTransform") val inputTransform: Transform
+    @JsName("inputRegion") val inputRegion: Region?
+    @JsName("excludesCompositionState") val excludesCompositionState: Boolean
+
+    @JsName("isScaling")
+    val isScaling: Boolean
+        get() = transform.isScaling
+    @JsName("isTranslating")
+    val isTranslating: Boolean
+        get() = transform.isTranslating
+    @JsName("isRotating")
+    val isRotating: Boolean
+        get() = transform.isRotating
+
+    /**
+     * Checks if the layer's active buffer is empty
+     *
+     * An active buffer is empty if it is not in the proto or if its height or width are 0
+     *
+     * @return
+     */
+    @JsName("isActiveBufferEmpty")
+    val isActiveBufferEmpty: Boolean
+        get() = activeBuffer.isEmpty
+
+    /**
+     * Converts flags to human readable tokens.
+     *
+     * @return
+     */
+    @JsName("verboseFlags")
+    val verboseFlags: String
+        get() {
+            val tokens = Flag.values().filter { (it.value and flags) != 0 }.map { it.name }
+
+            return if (tokens.isEmpty()) {
+                ""
+            } else {
+                "${tokens.joinToString("|")} (0x${flags.toString(16)})"
+            }
+        }
+
+    /**
+     * Checks if the [Layer] has a color
+     *
+     * @return
+     */
+    @JsName("fillsColor")
+    val fillsColor: Boolean
+        get() = color.isNotEmpty
+
+    /**
+     * Checks if the [Layer] draws a shadow
+     *
+     * @return
+     */
+    @JsName("drawsShadows")
+    val drawsShadows: Boolean
+        get() = shadowRadius > 0
+
+    /**
+     * Checks if the [Layer] has blur
+     *
+     * @return
+     */
+    @JsName("hasBlur")
+    val hasBlur: Boolean
+        get() = backgroundBlurRadius > 0
+
+    /**
+     * Checks if the [Layer] has rounded corners
+     *
+     * @return
+     */
+    @JsName("hasRoundedCorners")
+    val hasRoundedCorners: Boolean
+        get() = cornerRadius > 0
+
+    /**
+     * Checks if the [Layer] draws has effects, which include:
+     * - is a color layer
+     * - is an effects layers which [fillsColor] or [drawsShadows]
+     *
+     * @return
+     */
+    @JsName("hasEffects")
+    val hasEffects: Boolean
+        get() {
+            return fillsColor || drawsShadows
+        }
+
+    fun isAnimating(prevLayerState: ILayerProperties?): Boolean =
+        when (prevLayerState) {
+            // when there's no previous state, use a heuristic based on the transform
+            null -> !transform.isSimpleRotation
+            else ->
+                visibleRegion != prevLayerState.visibleRegion ||
+                    transform != prevLayerState.transform ||
+                    color != prevLayerState.color
+        }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Layer.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Layer.kt
new file mode 100644
index 0000000..28c5c7d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Layer.kt
@@ -0,0 +1,399 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a single layer with links to its parent and child layers.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class Layer
+private constructor(
+    val name: String,
+    val id: Int,
+    val parentId: Int,
+    val z: Int,
+    val currFrame: Long,
+    properties: ILayerProperties,
+) : ILayerProperties by properties {
+    val stableId: String = "$type $id $name"
+    var parent: Layer? = null
+    var zOrderRelativeOf: Layer? = null
+    var zOrderRelativeParentOf: Int = 0
+
+    /**
+     * Checks if the [Layer] is a root layer in the hierarchy
+     *
+     * @return
+     */
+    val isRootLayer: Boolean
+        get() = parent == null
+
+    private val _children = mutableListOf<Layer>()
+    private val _occludedBy = mutableListOf<Layer>()
+    private val _partiallyOccludedBy = mutableListOf<Layer>()
+    private val _coveredBy = mutableListOf<Layer>()
+    val children: Array<Layer>
+        get() = _children.toTypedArray()
+    val occludedBy: Array<Layer>
+        get() = _occludedBy.toTypedArray()
+    val partiallyOccludedBy: Array<Layer>
+        get() = _partiallyOccludedBy.toTypedArray()
+    val coveredBy: Array<Layer>
+        get() = _coveredBy.toTypedArray()
+    var isMissing: Boolean = false
+        internal set
+
+    /**
+     * Checks if the layer is hidden, that is, if its flags contain Flag.HIDDEN
+     *
+     * @return
+     */
+    val isHiddenByPolicy: Boolean
+        get() {
+            return (flags and Flag.HIDDEN.value) != 0x0 ||
+                // offscreen layer root has a unique layer id
+                id == 0x7FFFFFFD
+        }
+
+    /**
+     * Checks if the layer is visible.
+     *
+     * A layer is visible if:
+     * - it has an active buffer or has effects
+     * - is not hidden
+     * - is not transparent
+     * - not occluded by other layers
+     *
+     * @return
+     */
+    val isVisible: Boolean
+        get() {
+            val visibleRegion =
+                if (excludesCompositionState) {
+                    // Doesn't include state sent during composition like visible region and
+                    // composition type, so we fallback on the bounds as the visible region
+                    Region.from(this.bounds)
+                } else {
+                    this.visibleRegion ?: Region.EMPTY
+                }
+            return when {
+                isHiddenByParent -> false
+                isHiddenByPolicy -> false
+                isActiveBufferEmpty && !hasEffects -> false
+                occludedBy.isNotEmpty() -> false
+                else -> visibleRegion.isNotEmpty
+            }
+        }
+
+    /**
+     * Checks if the [Layer] is hidden by its parent
+     *
+     * @return
+     */
+    val isHiddenByParent: Boolean
+        get() =
+            !isRootLayer && (parent?.isHiddenByPolicy == true || parent?.isHiddenByParent == true)
+
+    /**
+     * Gets a description of why the layer is (in)visible
+     *
+     * @return
+     */
+    val visibilityReason: Array<String>
+        get() {
+            if (isVisible) {
+                return emptyArray()
+            }
+            val reasons = mutableListOf<String>()
+            if (isHiddenByPolicy) reasons.add("Flag is hidden")
+            if (isHiddenByParent) reasons.add("Hidden by parent ${parent?.name}")
+            if (isActiveBufferEmpty) reasons.add("Buffer is empty")
+            if (color.a == 0.0f) reasons.add("Alpha is 0")
+            if (bounds.isEmpty) reasons.add("Bounds is 0x0")
+            if (bounds.isEmpty && crop.isEmpty) reasons.add("Crop is 0x0")
+            if (!transform.isValid) reasons.add("Transform is invalid")
+            if (isRelativeOf && zOrderRelativeOf == null) {
+                reasons.add("RelativeOf layer has been removed")
+            }
+            if (isActiveBufferEmpty && !fillsColor && !drawsShadows && !hasBlur) {
+                reasons.add("does not have color fill, shadow or blur")
+            }
+            if (_occludedBy.isNotEmpty()) {
+                val occludedByLayers = _occludedBy.joinToString(", ") { "${it.name} (${it.id})" }
+                reasons.add("Layer is occluded by: $occludedByLayers")
+            }
+            if (visibleRegion?.isEmpty == true) {
+                reasons.add("Visible region calculated by Composition Engine is empty")
+            }
+            if (reasons.isEmpty()) reasons.add("Unknown")
+            return reasons.toTypedArray()
+        }
+
+    val zOrderPath: Array<Int>
+        get() {
+            val zOrderRelativeOf = zOrderRelativeOf
+            val zOrderPath =
+                when {
+                    zOrderRelativeOf != null -> zOrderRelativeOf.zOrderPath.toMutableList()
+                    parent != null -> parent?.zOrderPath?.toMutableList() ?: mutableListOf()
+                    else -> mutableListOf()
+                }
+            zOrderPath.add(z)
+            return zOrderPath.toTypedArray()
+        }
+
+    /**
+     * Returns true iff the [innerLayer] screen bounds are inside or equal to this layer's
+     * [screenBounds] and neither layers are rotating.
+     */
+    fun contains(innerLayer: Layer, crop: RectF = RectF.EMPTY): Boolean {
+        return if (!this.transform.isSimpleRotation || !innerLayer.transform.isSimpleRotation) {
+            false
+        } else {
+            val thisBounds: RectF
+            val innerLayerBounds: RectF
+            if (crop.isNotEmpty) {
+                thisBounds = this.screenBounds.crop(crop)
+                innerLayerBounds = innerLayer.screenBounds.crop(crop)
+            } else {
+                thisBounds = this.screenBounds
+                innerLayerBounds = innerLayer.screenBounds
+            }
+            thisBounds.contains(innerLayerBounds)
+        }
+    }
+
+    fun addChild(childLayer: Layer) {
+        _children.add(childLayer)
+    }
+
+    fun addOccludedBy(layers: Array<Layer>) {
+        _occludedBy.addAll(layers)
+    }
+
+    fun addPartiallyOccludedBy(layers: Array<Layer>) {
+        _partiallyOccludedBy.addAll(layers)
+    }
+
+    fun addCoveredBy(layers: Array<Layer>) {
+        _coveredBy.addAll(layers)
+    }
+
+    fun overlaps(other: Layer, crop: RectF = RectF.EMPTY): Boolean {
+        val thisBounds: RectF
+        val otherBounds: RectF
+        if (crop.isNotEmpty) {
+            thisBounds = this.screenBounds.crop(crop)
+            otherBounds = other.screenBounds.crop(crop)
+        } else {
+            thisBounds = this.screenBounds
+            otherBounds = other.screenBounds
+        }
+        return !thisBounds.intersection(otherBounds).isEmpty
+    }
+
+    override fun toString(): String {
+        return buildString {
+            append(name)
+
+            if (activeBuffer.isNotEmpty) {
+                append(" buffer:$activeBuffer")
+                append(" frame#$currFrame")
+            }
+
+            if (isVisible) {
+                append(" visible:$visibleRegion")
+            }
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Layer) return false
+
+        if (name != other.name) return false
+        if (id != other.id) return false
+        if (parentId != other.parentId) return false
+        if (z != other.z) return false
+        if (currFrame != other.currFrame) return false
+        if (stableId != other.stableId) return false
+        if (zOrderRelativeOf != other.zOrderRelativeOf) return false
+        if (zOrderRelativeParentOf != other.zOrderRelativeParentOf) return false
+        if (_occludedBy != other._occludedBy) return false
+        if (_partiallyOccludedBy != other._partiallyOccludedBy) return false
+        if (_coveredBy != other._coveredBy) return false
+        if (isMissing != other.isMissing) return false
+        if (visibleRegion != other.visibleRegion) return false
+        if (activeBuffer != other.activeBuffer) return false
+        if (flags != other.flags) return false
+        if (bounds != other.bounds) return false
+        if (color != other.color) return false
+        if (shadowRadius != other.shadowRadius) return false
+        if (cornerRadius != other.cornerRadius) return false
+        if (type != other.type) return false
+        if (transform != other.transform) return false
+        if (sourceBounds != other.sourceBounds) return false
+        if (effectiveScalingMode != other.effectiveScalingMode) return false
+        if (bufferTransform != other.bufferTransform) return false
+        if (hwcCompositionType != other.hwcCompositionType) return false
+        if (hwcCrop != other.hwcCrop) return false
+        if (hwcFrame != other.hwcFrame) return false
+        if (backgroundBlurRadius != other.backgroundBlurRadius) return false
+        if (crop != other.crop) return false
+        if (isRelativeOf != other.isRelativeOf) return false
+        if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false
+        if (stackId != other.stackId) return false
+        if (requestedTransform != other.requestedTransform) return false
+        if (requestedColor != other.requestedColor) return false
+        if (cornerRadiusCrop != other.cornerRadiusCrop) return false
+        if (inputTransform != other.inputTransform) return false
+        if (inputRegion != other.inputRegion) return false
+        if (screenBounds != other.screenBounds) return false
+        if (isOpaque != other.isOpaque) return false
+        if (excludesCompositionState != other.excludesCompositionState) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = visibleRegion?.hashCode() ?: 0
+        result = 31 * result + activeBuffer.hashCode()
+        result = 31 * result + flags
+        result = 31 * result + bounds.hashCode()
+        result = 31 * result + color.hashCode()
+        result = 31 * result + shadowRadius.hashCode()
+        result = 31 * result + cornerRadius.hashCode()
+        result = 31 * result + type.hashCode()
+        result = 31 * result + transform.hashCode()
+        result = 31 * result + sourceBounds.hashCode()
+        result = 31 * result + effectiveScalingMode
+        result = 31 * result + bufferTransform.hashCode()
+        result = 31 * result + hwcCompositionType.hashCode()
+        result = 31 * result + hwcCrop.hashCode()
+        result = 31 * result + hwcFrame.hashCode()
+        result = 31 * result + backgroundBlurRadius
+        result = 31 * result + crop.hashCode()
+        result = 31 * result + isRelativeOf.hashCode()
+        result = 31 * result + zOrderRelativeOfId
+        result = 31 * result + stackId
+        result = 31 * result + requestedTransform.hashCode()
+        result = 31 * result + requestedColor.hashCode()
+        result = 31 * result + cornerRadiusCrop.hashCode()
+        result = 31 * result + inputTransform.hashCode()
+        result = 31 * result + (inputRegion?.hashCode() ?: 0)
+        result = 31 * result + screenBounds.hashCode()
+        result = 31 * result + isOpaque.hashCode()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + id
+        result = 31 * result + parentId
+        result = 31 * result + z
+        result = 31 * result + currFrame.hashCode()
+        result = 31 * result + stableId.hashCode()
+        result = 31 * result + (zOrderRelativeOf?.hashCode() ?: 0)
+        result = 31 * result + zOrderRelativeParentOf
+        result = 31 * result + _children.hashCode()
+        result = 31 * result + _occludedBy.hashCode()
+        result = 31 * result + _partiallyOccludedBy.hashCode()
+        result = 31 * result + _coveredBy.hashCode()
+        result = 31 * result + isMissing.hashCode()
+        result = 31 * result + excludesCompositionState.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("from")
+        fun from(
+            name: String,
+            id: Int,
+            parentId: Int,
+            z: Int,
+            visibleRegion: Region,
+            activeBuffer: ActiveBuffer,
+            flags: Int,
+            bounds: RectF,
+            color: Color,
+            isOpaque: Boolean,
+            shadowRadius: Float,
+            cornerRadius: Float,
+            type: String,
+            screenBounds: RectF,
+            transform: Transform,
+            sourceBounds: RectF,
+            currFrame: Long,
+            effectiveScalingMode: Int,
+            bufferTransform: Transform,
+            hwcCompositionType: HwcCompositionType,
+            hwcCrop: RectF,
+            hwcFrame: Rect,
+            backgroundBlurRadius: Int,
+            crop: Rect?,
+            isRelativeOf: Boolean,
+            zOrderRelativeOfId: Int,
+            stackId: Int,
+            requestedTransform: Transform,
+            requestedColor: Color,
+            cornerRadiusCrop: RectF,
+            inputTransform: Transform,
+            inputRegion: Region?,
+            excludesCompositionState: Boolean
+        ): Layer {
+            val properties =
+                LayerProperties.from(
+                    visibleRegion,
+                    activeBuffer,
+                    flags,
+                    bounds,
+                    color,
+                    isOpaque,
+                    shadowRadius,
+                    cornerRadius,
+                    type,
+                    screenBounds,
+                    transform,
+                    sourceBounds,
+                    effectiveScalingMode,
+                    bufferTransform,
+                    hwcCompositionType,
+                    hwcCrop,
+                    hwcFrame,
+                    backgroundBlurRadius,
+                    crop,
+                    isRelativeOf,
+                    zOrderRelativeOfId,
+                    stackId,
+                    requestedTransform,
+                    requestedColor,
+                    cornerRadiusCrop,
+                    inputTransform,
+                    inputRegion,
+                    excludesCompositionState
+                )
+            return Layer(name, id, parentId, z, currFrame, properties)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerProperties.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerProperties.kt
new file mode 100644
index 0000000..fc2c58a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerProperties.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/** {@inheritDoc} */
+@JsExport
+class LayerProperties
+private constructor(
+    override val visibleRegion: Region = Region.EMPTY,
+    override val activeBuffer: ActiveBuffer = ActiveBuffer.EMPTY,
+    override val flags: Int = 0,
+    override val bounds: RectF = RectF.EMPTY,
+    override val color: Color = Color.EMPTY,
+    private val _isOpaque: Boolean = false,
+    override val shadowRadius: Float = 0f,
+    override val cornerRadius: Float = 0f,
+    override val type: String = "",
+    override val screenBounds: RectF = RectF.EMPTY,
+    override val transform: Transform = Transform.EMPTY,
+    override val sourceBounds: RectF = RectF.EMPTY,
+    override val effectiveScalingMode: Int = 0,
+    override val bufferTransform: Transform = Transform.EMPTY,
+    override val hwcCompositionType: HwcCompositionType = HwcCompositionType.INVALID,
+    override val hwcCrop: RectF = RectF.EMPTY,
+    override val hwcFrame: Rect = Rect.EMPTY,
+    override val backgroundBlurRadius: Int = 0,
+    override val crop: Rect = Rect.EMPTY,
+    override val isRelativeOf: Boolean = false,
+    override val zOrderRelativeOfId: Int = 0,
+    override val stackId: Int = 0,
+    override val requestedTransform: Transform = Transform.EMPTY,
+    override val requestedColor: Color = Color.EMPTY,
+    override val cornerRadiusCrop: RectF = RectF.EMPTY,
+    override val inputTransform: Transform = Transform.EMPTY,
+    override val inputRegion: Region? = null,
+    override val excludesCompositionState: Boolean = false
+) : ILayerProperties {
+    override val isOpaque: Boolean = if (color.a != 1.0f) false else _isOpaque
+
+    override fun hashCode(): Int {
+        var result = visibleRegion.hashCode()
+        result = 31 * result + activeBuffer.hashCode()
+        result = 31 * result + flags
+        result = 31 * result + bounds.hashCode()
+        result = 31 * result + color.hashCode()
+        result = 31 * result + _isOpaque.hashCode()
+        result = 31 * result + shadowRadius.hashCode()
+        result = 31 * result + cornerRadius.hashCode()
+        result = 31 * result + type.hashCode()
+        result = 31 * result + screenBounds.hashCode()
+        result = 31 * result + transform.hashCode()
+        result = 31 * result + sourceBounds.hashCode()
+        result = 31 * result + effectiveScalingMode
+        result = 31 * result + bufferTransform.hashCode()
+        result = 31 * result + hwcCompositionType.hashCode()
+        result = 31 * result + hwcCrop.hashCode()
+        result = 31 * result + hwcFrame.hashCode()
+        result = 31 * result + backgroundBlurRadius
+        result = 31 * result + crop.hashCode()
+        result = 31 * result + isRelativeOf.hashCode()
+        result = 31 * result + zOrderRelativeOfId
+        result = 31 * result + stackId
+        result = 31 * result + requestedTransform.hashCode()
+        result = 31 * result + requestedColor.hashCode()
+        result = 31 * result + cornerRadiusCrop.hashCode()
+        result = 31 * result + inputTransform.hashCode()
+        result = 31 * result + (inputRegion?.hashCode() ?: 0)
+        result = 31 * result + screenBounds.hashCode()
+        result = 31 * result + isOpaque.hashCode()
+        result = 31 * result + excludesCompositionState.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "LayerProperties(visibleRegion=$visibleRegion, activeBuffer=$activeBuffer, " +
+            "flags=$flags, bounds=$bounds, color=$color, _isOpaque=$_isOpaque, " +
+            "shadowRadius=$shadowRadius, cornerRadius=$cornerRadius, type='$type', " +
+            "screenBounds=$screenBounds, transform=$transform, sourceBounds=$sourceBounds, " +
+            "effectiveScalingMode=$effectiveScalingMode, bufferTransform=$bufferTransform, " +
+            "hwcCompositionType=$hwcCompositionType, hwcCrop=$hwcCrop, hwcFrame=$hwcFrame, " +
+            "backgroundBlurRadius=$backgroundBlurRadius, crop=$crop, isRelativeOf=$isRelativeOf, " +
+            "zOrderRelativeOfId=$zOrderRelativeOfId, stackId=$stackId, " +
+            "requestedTransform=$requestedTransform, requestedColor=$requestedColor, " +
+            "cornerRadiusCrop=$cornerRadiusCrop, inputTransform=$inputTransform, " +
+            "inputRegion=$inputRegion, screenBounds=$screenBounds, isOpaque=$isOpaque, " +
+            "excludesCompositionState=$excludesCompositionState)"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is LayerProperties) return false
+
+        if (visibleRegion != other.visibleRegion) return false
+        if (activeBuffer != other.activeBuffer) return false
+        if (flags != other.flags) return false
+        if (bounds != other.bounds) return false
+        if (color != other.color) return false
+        if (_isOpaque != other._isOpaque) return false
+        if (shadowRadius != other.shadowRadius) return false
+        if (cornerRadius != other.cornerRadius) return false
+        if (type != other.type) return false
+        if (screenBounds != other.screenBounds) return false
+        if (transform != other.transform) return false
+        if (sourceBounds != other.sourceBounds) return false
+        if (effectiveScalingMode != other.effectiveScalingMode) return false
+        if (bufferTransform != other.bufferTransform) return false
+        if (hwcCompositionType != other.hwcCompositionType) return false
+        if (hwcCrop != other.hwcCrop) return false
+        if (hwcFrame != other.hwcFrame) return false
+        if (backgroundBlurRadius != other.backgroundBlurRadius) return false
+        if (crop != other.crop) return false
+        if (isRelativeOf != other.isRelativeOf) return false
+        if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false
+        if (stackId != other.stackId) return false
+        if (requestedTransform != other.requestedTransform) return false
+        if (requestedColor != other.requestedColor) return false
+        if (cornerRadiusCrop != other.cornerRadiusCrop) return false
+        if (inputTransform != other.inputTransform) return false
+        if (inputRegion != other.inputRegion) return false
+        if (screenBounds != other.screenBounds) return false
+        if (isOpaque != other.isOpaque) return false
+        if (excludesCompositionState != other.excludesCompositionState) return false
+
+        return true
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: LayerProperties
+            get() = withCache { LayerProperties() }
+
+        @JsName("from")
+        fun from(
+            visibleRegion: Region,
+            activeBuffer: ActiveBuffer,
+            flags: Int,
+            bounds: RectF,
+            color: Color,
+            isOpaque: Boolean,
+            shadowRadius: Float,
+            cornerRadius: Float,
+            type: String,
+            screenBounds: RectF,
+            transform: Transform,
+            sourceBounds: RectF,
+            effectiveScalingMode: Int,
+            bufferTransform: Transform,
+            hwcCompositionType: HwcCompositionType,
+            hwcCrop: RectF,
+            hwcFrame: Rect,
+            backgroundBlurRadius: Int,
+            crop: Rect?,
+            isRelativeOf: Boolean,
+            zOrderRelativeOfId: Int,
+            stackId: Int,
+            requestedTransform: Transform,
+            requestedColor: Color,
+            cornerRadiusCrop: RectF,
+            inputTransform: Transform,
+            inputRegion: Region?,
+            excludesCompositionState: Boolean
+        ): ILayerProperties {
+            return withCache {
+                LayerProperties(
+                    visibleRegion,
+                    activeBuffer,
+                    flags,
+                    bounds,
+                    color,
+                    isOpaque,
+                    shadowRadius,
+                    cornerRadius,
+                    type,
+                    screenBounds,
+                    transform,
+                    sourceBounds,
+                    effectiveScalingMode,
+                    bufferTransform,
+                    hwcCompositionType,
+                    hwcCrop,
+                    hwcFrame,
+                    backgroundBlurRadius,
+                    crop ?: Rect.EMPTY,
+                    isRelativeOf,
+                    zOrderRelativeOfId,
+                    stackId,
+                    requestedTransform,
+                    requestedColor,
+                    cornerRadiusCrop,
+                    inputTransform,
+                    inputRegion,
+                    excludesCompositionState
+                )
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntry.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntry.kt
new file mode 100644
index 0000000..deb6575
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntry.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.CrossPlatform
+import android.tools.common.ITraceEntry
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a single Layer trace entry.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class LayerTraceEntry(
+    @JsName("elapsedTimestamp") val elapsedTimestamp: Long,
+    @JsName("clockTimestamp") val clockTimestamp: Long?,
+    @JsName("hwcBlob") val hwcBlob: String,
+    @JsName("where") val where: String,
+    @JsName("displays") val displays: Array<Display>,
+    @JsName("vSyncId") val vSyncId: Long,
+    _rootLayers: Array<Layer>
+) : ITraceEntry {
+    override val timestamp =
+        CrossPlatform.timestamp.from(
+            systemUptimeNanos = elapsedTimestamp,
+            unixNanos = clockTimestamp
+        )
+
+    @JsName("stableId")
+    val stableId: String = this::class.simpleName ?: error("Unable to determine class")
+
+    @JsName("flattenedLayers") val flattenedLayers: Array<Layer> = fillFlattenedLayers(_rootLayers)
+
+    // for winscope
+    @JsName("isVisible") val isVisible: Boolean = true
+
+    @JsName("visibleLayers")
+    val visibleLayers: Array<Layer>
+        get() = flattenedLayers.filter { it.isVisible }.toTypedArray()
+
+    @JsName("children")
+    val children: Array<Layer>
+        get() = flattenedLayers.filter { it.isRootLayer }.toTypedArray()
+
+    @JsName("physicalDisplay")
+    val physicalDisplay: Display?
+        get() = displays.firstOrNull { !it.isVirtual }
+
+    @JsName("physicalDisplayBounds")
+    val physicalDisplayBounds: Rect?
+        get() = physicalDisplay?.layerStackSpace
+
+    /**
+     * @return A [Layer] matching [componentMatcher] with a non-empty active buffer, or null if no
+     * layer matches [componentMatcher] or if the matching layer's buffer is empty
+     *
+     * @param componentMatcher Components to search
+     */
+    fun getLayerWithBuffer(componentMatcher: IComponentMatcher): Layer? {
+        return flattenedLayers.firstOrNull {
+            componentMatcher.layerMatchesAnyOf(it) && it.activeBuffer.isNotEmpty
+        }
+    }
+
+    /** @return The [Layer] with [layerId], or null if the layer is not found */
+    fun getLayerById(layerId: Int): Layer? = this.flattenedLayers.firstOrNull { it.id == layerId }
+
+    /**
+     * Checks if any layer matching [componentMatcher] in the screen is animating.
+     *
+     * The screen is animating when a layer is not simple rotation, of when the pip overlay layer is
+     * visible
+     *
+     * @param componentMatcher Components to search
+     */
+    fun isAnimating(
+        prevState: LayerTraceEntry?,
+        componentMatcher: IComponentMatcher? = null
+    ): Boolean {
+        val curLayers =
+            visibleLayers.filter {
+                componentMatcher == null || componentMatcher.layerMatchesAnyOf(it)
+            }
+        val currIds = visibleLayers.map { it.id }
+        val prevStateLayers =
+            prevState?.visibleLayers?.filter { currIds.contains(it.id) } ?: emptyList()
+        val layersAnimating =
+            curLayers.any { currLayer ->
+                val prevLayer = prevStateLayers.firstOrNull { it.id == currLayer.id }
+                currLayer.isAnimating(prevLayer)
+            }
+        val pipAnimating = isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+        return layersAnimating || pipAnimating
+    }
+
+    /**
+     * Check if at least one window matching [componentMatcher] is visible.
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("isVisibleComponent")
+    fun isVisible(componentMatcher: IComponentMatcher): Boolean =
+        componentMatcher.layerMatchesAnyOf(visibleLayers)
+
+    /** @return A [LayersTrace] object containing this state as its only entry */
+    fun asTrace(): LayersTrace = LayersTrace(arrayOf(this))
+
+    override fun toString(): String = "${timestamp}ns"
+
+    override fun equals(other: Any?): Boolean {
+        return other is LayerTraceEntry && other.timestamp == this.timestamp
+    }
+
+    override fun hashCode(): Int {
+        var result = timestamp.hashCode()
+        result = 31 * result + hwcBlob.hashCode()
+        result = 31 * result + where.hashCode()
+        result = 31 * result + displays.contentHashCode()
+        result = 31 * result + isVisible.hashCode()
+        result = 31 * result + flattenedLayers.contentHashCode()
+        return result
+    }
+
+    private fun fillFlattenedLayers(rootLayers: Array<Layer>): Array<Layer> {
+        val layers = mutableListOf<Layer>()
+        val roots = rootLayers.fillOcclusionState().toMutableList()
+        while (roots.isNotEmpty()) {
+            val layer = roots.removeAt(0)
+            layers.add(layer)
+            roots.addAll(layer.children)
+        }
+        return layers.toTypedArray()
+    }
+
+    private fun Array<Layer>.topDownTraversal(): List<Layer> {
+        return this.sortedBy { it.z }.flatMap { it.topDownTraversal() }
+    }
+
+    private fun Layer.topDownTraversal(): List<Layer> {
+        val traverseList = mutableListOf(this)
+
+        this.children
+            .sortedBy { it.z }
+            .forEach { childLayer -> traverseList.addAll(childLayer.topDownTraversal()) }
+
+        return traverseList
+    }
+
+    private fun Array<Layer>.fillOcclusionState(): Array<Layer> {
+        val traversalList = topDownTraversal().reversed()
+
+        val opaqueLayers = mutableListOf<Layer>()
+        val transparentLayers = mutableListOf<Layer>()
+
+        traversalList.forEach { layer ->
+            val visible = layer.isVisible
+            val displaySize =
+                displays
+                    .firstOrNull { it.layerStackId == layer.stackId }
+                    ?.layerStackSpace
+                    ?.toRectF()
+                    ?: RectF.EMPTY
+
+            if (visible) {
+                val occludedBy =
+                    opaqueLayers
+                        .filter {
+                            it.stackId == layer.stackId &&
+                                it.contains(layer, displaySize) &&
+                                (!it.hasRoundedCorners || (layer.cornerRadius == it.cornerRadius))
+                        }
+                        .toTypedArray()
+                layer.addOccludedBy(occludedBy)
+                val partiallyOccludedBy =
+                    opaqueLayers
+                        .filter {
+                            it.stackId == layer.stackId &&
+                                it.overlaps(layer, displaySize) &&
+                                it !in layer.occludedBy
+                        }
+                        .toTypedArray()
+                layer.addPartiallyOccludedBy(partiallyOccludedBy)
+                val coveredBy =
+                    transparentLayers
+                        .filter { it.stackId == layer.stackId && it.overlaps(layer, displaySize) }
+                        .toTypedArray()
+                layer.addCoveredBy(coveredBy)
+
+                if (layer.isOpaque) {
+                    opaqueLayers.add(layer)
+                } else {
+                    transparentLayers.add(layer)
+                }
+            }
+        }
+
+        return this
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilder.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilder.kt
new file mode 100644
index 0000000..1e51428
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilder.kt
@@ -0,0 +1,254 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.Timestamp
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/** Builder for LayerTraceEntries */
+@JsExport
+class LayerTraceEntryBuilder {
+    private var elapsedTimestamp: Long = 0L
+    private var realTimestamp: Long? = null
+    private var orphanLayerCallback: ((Layer) -> Boolean)? = null
+    private val orphans = mutableListOf<Layer>()
+    private var layers: MutableMap<Int, Layer> = mutableMapOf()
+    private var ignoreVirtualDisplay = false
+    private var ignoreLayersStackMatchNoDisplay = false
+    private var timestamp: Timestamp? = null
+    private var displays: Array<Display> = emptyArray()
+    private var vSyncId: Long = 0L
+    private var hwcBlob: String = ""
+    private var where: String = ""
+    private var duplicateLayerCallback: ((Layer) -> Boolean) = {
+        error("Duplicate layer id found: ${it.id}")
+    }
+
+    @JsName("setVSyncId")
+    fun setVSyncId(vSyncId: String): LayerTraceEntryBuilder =
+    // Necessary for compatibility with JS number type
+    apply {
+        this.vSyncId = vSyncId.toLong()
+    }
+
+    @JsName("setHwcBlob")
+    fun setHwcBlob(hwcBlob: String): LayerTraceEntryBuilder = apply { this.hwcBlob = hwcBlob }
+
+    @JsName("setWhere")
+    fun setWhere(where: String): LayerTraceEntryBuilder = apply { this.where = where }
+
+    @JsName("setDisplays")
+    fun setDisplays(displays: Array<Display>): LayerTraceEntryBuilder = apply {
+        this.displays = displays
+    }
+
+    @JsName("setElapsedTimestamp")
+    fun setElapsedTimestamp(timestamp: String): LayerTraceEntryBuilder =
+    // Necessary for compatibility with JS number type
+    apply {
+        this.elapsedTimestamp = timestamp.toLong()
+    }
+
+    @JsName("setRealToElapsedTimeOffsetNs")
+    fun setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNs: String?): LayerTraceEntryBuilder =
+        apply {
+            this.realTimestamp =
+                if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs.toLong() != 0L) {
+                    realToElapsedTimeOffsetNs.toLong() + elapsedTimestamp
+                } else {
+                    null
+                }
+        }
+
+    @JsName("setLayers")
+    fun setLayers(layers: Array<Layer>): LayerTraceEntryBuilder = apply {
+        val result = mutableMapOf<Int, Layer>()
+        layers.forEach { layer ->
+            val id = layer.id
+            if (result.containsKey(id)) {
+                duplicateLayerCallback(layer)
+            }
+            result[id] = layer
+        }
+
+        this.layers = result
+    }
+
+    @JsName("setOrphanLayerCallback")
+    fun setOrphanLayerCallback(value: ((Layer) -> Boolean)?): LayerTraceEntryBuilder = apply {
+        this.orphanLayerCallback = value
+    }
+
+    @JsName("setDuplicateLayerCallback")
+    fun setDuplicateLayerCallback(value: ((Layer) -> Boolean)): LayerTraceEntryBuilder = apply {
+        this.duplicateLayerCallback = value
+    }
+
+    private fun notifyOrphansLayers() {
+        val callback = this.orphanLayerCallback ?: return
+
+        // Fail if we find orphan layers.
+        orphans.forEach { orphan ->
+            // Workaround for b/141326137, ignore the existence of an orphan layer
+            if (callback.invoke(orphan)) {
+                return@forEach
+            }
+            throw RuntimeException(
+                ("Failed to parse layers trace. Found orphan layer with id = ${orphan.id}" +
+                    " with parentId = ${orphan.parentId}")
+            )
+        }
+    }
+
+    /**
+     * Update the parent layers or each trace
+     *
+     * @return root layer
+     */
+    private fun updateParents() {
+        for (layer in layers.values) {
+            val parentId = layer.parentId
+
+            val parentLayer = layers[parentId]
+            if (parentLayer == null) {
+                orphans.add(layer)
+                continue
+            }
+            parentLayer.addChild(layer)
+            layer.parent = parentLayer
+        }
+    }
+
+    /**
+     * Update the parent layers or each trace
+     *
+     * @return root layer
+     */
+    private fun updateRelZParents() {
+        for (layer in layers.values) {
+            val parentId = layer.zOrderRelativeOfId
+
+            val parentLayer = layers[parentId]
+            if (parentLayer == null) {
+                layer.zOrderRelativeParentOf = parentId
+                continue
+            }
+            layer.zOrderRelativeOf = parentLayer
+        }
+    }
+
+    private fun computeRootLayers(): List<Layer> {
+        updateParents()
+        updateRelZParents()
+
+        // Find all root layers (any sibling of the root layer is considered a root layer in the
+        // trace)
+        val rootLayers = mutableListOf<Layer>()
+
+        // Getting the first orphan works because when dumping the layers, the root layer comes
+        // first, and given that orphans are added in the same order as the layers are provided
+        // in the first orphan layer should be the root layer.
+        if (orphans.isNotEmpty()) {
+            val firstRoot = orphans.first()
+            orphans.remove(firstRoot)
+            rootLayers.add(firstRoot)
+
+            val remainingRoots = orphans.filter { it.parentId == firstRoot.parentId }
+            rootLayers.addAll(remainingRoots)
+
+            // Remove RootLayers from orphans
+            orphans.removeAll(rootLayers)
+        }
+
+        return rootLayers
+    }
+
+    private fun filterOutLayersInVirtualDisplays(roots: List<Layer>): List<Layer> {
+        val physicalDisplays = displays.filterNot { it.isVirtual }.map { it.layerStackId }
+
+        return roots.filter { physicalDisplays.contains(it.stackId) }
+    }
+
+    private fun filterOutVirtualDisplays(displays: List<Display>): List<Display> {
+        return displays.filterNot { it.isVirtual }
+    }
+
+    private fun filterOutOffDisplays(displays: List<Display>): List<Display> {
+        return displays.filterNot { it.isOff }
+    }
+
+    private fun filterOutLayersStackMatchNoDisplay(roots: List<Layer>): List<Layer> {
+        val displayStacks = displays.map { it.layerStackId }
+        return roots.filter { displayStacks.contains(it.stackId) }
+    }
+
+    /**
+     * Defines if virtual displays and the layers belonging to virtual displays (e.g., Screen
+     * Recording) should be ignored while parsing the entry
+     *
+     * @param ignore If the layers from virtual displays should be ignored or not
+     */
+    @JsName("ignoreVirtualDisplay")
+    fun ignoreVirtualDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
+        this.ignoreVirtualDisplay = ignore
+    }
+
+    /**
+     * Ignore layers whose stack ID doesn't match any display. This is the case, for example, when
+     * the device screen is off, or for layers that have not yet been removed after a display change
+     * (e.g., virtual screen recording display removed)
+     *
+     * @param ignore If the layers not matching any stack id should be removed or not
+     */
+    @JsName("ignoreLayersStackMatchNoDisplay")
+    fun ignoreLayersStackMatchNoDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
+        this.ignoreLayersStackMatchNoDisplay = ignore
+    }
+
+    /** Constructs the layer hierarchy from a flattened list of layers. */
+    @JsName("build")
+    fun build(): LayerTraceEntry {
+        val allRoots = computeRootLayers()
+        var filteredRoots = allRoots
+        var filteredDisplays = displays.toList()
+
+        if (ignoreLayersStackMatchNoDisplay) {
+            filteredRoots = filterOutLayersStackMatchNoDisplay(filteredRoots)
+        }
+
+        if (ignoreVirtualDisplay) {
+            filteredRoots = filterOutLayersInVirtualDisplays(filteredRoots)
+            filteredDisplays = filterOutVirtualDisplays(filteredDisplays)
+        }
+
+        filteredDisplays = filterOutOffDisplays(filteredDisplays)
+
+        // Fail if we find orphan layers.
+        notifyOrphansLayers()
+
+        return LayerTraceEntry(
+            elapsedTimestamp,
+            realTimestamp,
+            hwcBlob,
+            where,
+            filteredDisplays.toTypedArray(),
+            vSyncId,
+            filteredRoots.toTypedArray(),
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayersTrace.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayersTrace.kt
new file mode 100644
index 0000000..9880726
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/LayersTrace.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Contains a collection of parsed Layers trace entries and assertions to apply over a single entry.
+ *
+ * Each entry is parsed into a list of [LayerTraceEntry] objects.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+data class LayersTrace(override val entries: Array<LayerTraceEntry>) : ITrace<LayerTraceEntry> {
+    override fun toString(): String {
+        return "LayersTrace(Start: ${entries.firstOrNull()}, " + "End: ${entries.lastOrNull()})"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is LayersTrace) return false
+
+        if (!entries.contentEquals(other.entries)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return entries.contentHashCode()
+    }
+
+    @JsName("vSyncSlice")
+    fun vSyncSlice(from: Int, to: Int): LayersTrace {
+        return LayersTrace(
+            this.entries
+                .dropWhile { it.vSyncId < from }
+                .dropLastWhile { it.vSyncId > to }
+                .toTypedArray()
+        )
+    }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): LayersTrace {
+        return LayersTrace(
+            entries
+                .dropWhile { it.timestamp < startTimestamp }
+                .dropLastWhile { it.timestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transaction.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transaction.kt
new file mode 100644
index 0000000..34007a7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transaction.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.traces.surfaceflinger
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+data class Transaction(
+    @JsName("pid") val pid: Int,
+    @JsName("uid") val uid: Int,
+    @JsName("requestedVSyncId") val requestedVSyncId: Long,
+    @JsName("postTime") val postTime: Long,
+    @JsName("id") val id: Long,
+) {
+    lateinit var appliedInEntry: TransactionsTraceEntry
+        internal set
+
+    val appliedVSyncId: Long
+        get() = appliedInEntry.vSyncId
+
+    override fun toString(): String {
+        return "Transaction#${hashCode().toString(16)}" +
+            "(pid=$pid, uid=$uid, requestedVSyncId=$requestedVSyncId, postTime=$postTime, " +
+            "id=$id)"
+    }
+
+    companion object {
+        @JsName("emptyTransaction")
+        fun emptyTransaction(): Transaction {
+            return Transaction(0, 0, 0, 0, 0)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTrace.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTrace.kt
new file mode 100644
index 0000000..a31771a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTrace.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+class TransactionsTrace(override val entries: Array<TransactionsTraceEntry>) :
+    ITrace<TransactionsTraceEntry> {
+
+    init {
+        val alwaysIncreasing =
+            entries
+                .toList()
+                .zipWithNext { prev, next ->
+                    prev.timestamp.elapsedNanos < next.timestamp.elapsedNanos
+                }
+                .all { it }
+        require(alwaysIncreasing) { "Transaction timestamp not always increasing..." }
+    }
+
+    @JsName("allTransactions")
+    val allTransactions: List<Transaction> = entries.toList().flatMap { it.transactions.toList() }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): TransactionsTrace {
+        return TransactionsTrace(
+            entries
+                .dropWhile { it.timestamp < startTimestamp }
+                .dropLastWhile { it.timestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTraceEntry.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTraceEntry.kt
new file mode 100644
index 0000000..565cdb6
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/TransactionsTraceEntry.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+class TransactionsTraceEntry(
+    override val timestamp: Timestamp,
+    @JsName("vSyncId") val vSyncId: Long,
+    @JsName("transactions") val transactions: Array<Transaction>
+) : ITraceEntry
diff --git a/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transform.kt b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transform.kt
new file mode 100644
index 0000000..35e3517
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/surfaceflinger/Transform.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Matrix33
+import android.tools.common.datatypes.RectF
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Wrapper for TransformProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
+ *
+ * This class is used by flicker and Winscope
+ */
+@JsExport
+class Transform
+private constructor(@JsName("type") val type: Int?, @JsName("matrix") val matrix: Matrix33) {
+
+    /**
+     * Returns true if the applying the transform on an an axis aligned rectangle results in another
+     * axis aligned rectangle.
+     */
+    @JsName("isSimpleRotation")
+    val isSimpleRotation: Boolean = !(type?.isFlagSet(ROT_INVALID_VAL) ?: true)
+
+    /**
+     * The transformation matrix is defined as the product of: | cos(a) -sin(a) | \/ | X 0 | |
+     * sin(a) cos(a) | /\ | 0 Y |
+     *
+     * where a is a rotation angle, and X and Y are scaling factors. A transformation matrix is
+     * invalid when either X or Y is zero, as a rotation matrix is valid for any angle. When either
+     * X or Y is 0, then the scaling matrix is not invertible, which makes the transformation matrix
+     * not invertible as well. A 2D matrix with components | A B | is not invertible if and only if
+     * AD - BC = 0.
+     * ```
+     *            | C D |
+     * ```
+     * This check is included above.
+     */
+    @JsName("isValid")
+    val isValid: Boolean
+        get() {
+            // determinant of transform
+            return matrix.dsdx * matrix.dtdy != matrix.dtdx * matrix.dsdy
+        }
+
+    @JsName("isScaling")
+    val isScaling: Boolean
+        get() = type?.isFlagSet(SCALE_VAL) ?: false
+    @JsName("isTranslating")
+    val isTranslating: Boolean
+        get() = type?.isFlagSet(TRANSLATE_VAL) ?: false
+    @JsName("isRotating")
+    val isRotating: Boolean
+        get() = type?.isFlagSet(ROTATE_VAL) ?: false
+
+    fun getRotation(): Rotation {
+        if (type == null) {
+            return Rotation.ROTATION_0
+        }
+
+        return when {
+            type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL) -> Rotation.ROTATION_0
+            type.isFlagSet(ROT_90_VAL) -> Rotation.ROTATION_90
+            type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> Rotation.ROTATION_180
+            type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> Rotation.ROTATION_270
+            else -> Rotation.ROTATION_0
+        }
+    }
+
+    private val typeFlags: Array<String>
+        get() {
+            if (type == null) {
+                return arrayOf("IDENTITY")
+            }
+
+            val result = mutableListOf<String>()
+
+            if (type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL)) {
+                result.add("IDENTITY")
+            }
+
+            if (type.isFlagSet(SCALE_VAL)) {
+                result.add("SCALE")
+            }
+
+            if (type.isFlagSet(TRANSLATE_VAL)) {
+                result.add("TRANSLATE")
+            }
+
+            when {
+                type.isFlagSet(ROT_INVALID_VAL) -> result.add("ROT_INVALID")
+                type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_270")
+                type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_180")
+                else -> {
+                    if (type.isFlagSet(ROT_90_VAL)) {
+                        result.add("ROT_90")
+                    }
+                    if (type.isFlagSet(FLIP_V_VAL)) {
+                        result.add("FLIP_V")
+                    }
+                    if (type.isFlagSet(FLIP_H_VAL)) {
+                        result.add("FLIP_H")
+                    }
+                }
+            }
+
+            if (result.isEmpty()) {
+                throw RuntimeException("Unknown transform type $type")
+            }
+
+            return result.toTypedArray()
+        }
+
+    @JsName("prettyPrint")
+    fun prettyPrint(): String {
+        val transformType = typeFlags.joinToString("|")
+
+        if (isSimpleTransform(type)) {
+            return transformType
+        }
+
+        return "$transformType ${matrix.prettyPrint()}"
+    }
+
+    @JsName("getTypeAsString")
+    fun getTypeAsString(): String {
+        return typeFlags.joinToString("|")
+    }
+
+    override fun toString(): String = prettyPrint()
+
+    @JsName("apply")
+    fun apply(bounds: RectF?): RectF {
+        return multiplyRect(matrix, bounds ?: RectF.EMPTY)
+    }
+
+    private data class Vec2(val x: Float, val y: Float)
+
+    private fun multiplyRect(matrix: Matrix33, rect: RectF): RectF {
+        //          |dsdx dsdy  tx|         | left, top         |
+        // matrix = |dtdx dtdy  ty|  rect = |                   |
+        //          |0    0     1 |         |     right, bottom |
+
+        val leftTop = multiplyVec2(matrix, rect.left, rect.top)
+        val rightTop = multiplyVec2(matrix, rect.right, rect.top)
+        val leftBottom = multiplyVec2(matrix, rect.left, rect.bottom)
+        val rightBottom = multiplyVec2(matrix, rect.right, rect.bottom)
+
+        return RectF.from(
+            left = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f,
+            top = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f,
+            right = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f,
+            bottom = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f
+        )
+    }
+
+    private fun multiplyVec2(matrix: Matrix33, x: Float, y: Float): Vec2 {
+        // |dsdx dsdy  tx|     | x |
+        // |dtdx dtdy  ty|  x  | y |
+        // |0    0     1 |     | 1 |
+        return Vec2(
+            matrix.dsdx * x + matrix.dsdy * y + matrix.tx,
+            matrix.dtdx * x + matrix.dtdy * y + matrix.ty
+        )
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Transform) return false
+
+        if (type != other.type) return false
+        if (matrix != other.matrix) return false
+        if (isSimpleRotation != other.isSimpleRotation) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = type ?: 0
+        result = 31 * result + matrix.hashCode()
+        result = 31 * result + isSimpleRotation.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Transform
+            get() = withCache { Transform(type = null, matrix = Matrix33.EMPTY) }
+        /* transform type flags */
+        @JsName("TRANSLATE_VAL") const val TRANSLATE_VAL = 0x0001
+        @JsName("ROTATE_VAL") const val ROTATE_VAL = 0x0002
+        @JsName("SCALE_VAL") const val SCALE_VAL = 0x0004
+
+        /* orientation flags */
+        @JsName("FLIP_H_VAL") const val FLIP_H_VAL = 0x0100 // (1 << 0 << 8)
+        @JsName("FLIP_V_VAL") const val FLIP_V_VAL = 0x0200 // (1 << 1 << 8)
+        @JsName("ROT_90_VAL") const val ROT_90_VAL = 0x0400 // (1 << 2 << 8)
+        @JsName("ROT_INVALID_VAL") const val ROT_INVALID_VAL = 0x8000 // (0x80 << 8)
+
+        @JsName("isSimpleTransform")
+        fun isSimpleTransform(type: Int?): Boolean {
+            return type?.isFlagClear(ROT_INVALID_VAL or SCALE_VAL) ?: false
+        }
+
+        fun Int.isFlagClear(bits: Int): Boolean {
+            return this and bits == 0
+        }
+
+        fun Int.isFlagSet(bits: Int): Boolean {
+            return this and bits == bits
+        }
+
+        @JsName("from")
+        fun from(type: Int?, matrix: Matrix33): Transform = withCache { Transform(type, matrix) }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/Activity.kt b/libraries/flicker/src/android/tools/common/traces/wm/Activity.kt
new file mode 100644
index 0000000..16367e0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/Activity.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents an activity in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class Activity(
+    name: String,
+    @JsName("state") val state: String,
+    visible: Boolean,
+    @JsName("frontOfTask") val frontOfTask: Boolean,
+    @JsName("procId") val procId: Int,
+    @JsName("isTranslucent") val isTranslucent: Boolean,
+    windowContainer: WindowContainer
+) : WindowContainer(windowContainer, name, visible) {
+    /**
+     * Checks if the activity contains a [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("getWindows")
+    fun getWindows(componentMatcher: IComponentMatcher): Array<WindowState> = getWindows {
+        componentMatcher.windowMatchesAnyOf(it)
+    }
+
+    /**
+     * Checks if the activity contains a [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("hasWindow")
+    fun hasWindow(componentMatcher: IComponentMatcher): Boolean =
+        getWindows(componentMatcher).isNotEmpty()
+
+    @JsName("hasWindowState")
+    internal fun hasWindowState(windowState: WindowState): Boolean =
+        getWindows { windowState == it }.isNotEmpty()
+
+    @JsName("isTablet")
+    private fun getWindows(predicate: (WindowState) -> Boolean) =
+        collectDescendants<WindowState> { predicate(it) }
+
+    override fun toString(): String {
+        return "${this::class.simpleName}: {$token $title} state=$state visible=$isVisible"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Activity) return false
+
+        if (state != other.state) return false
+        if (frontOfTask != other.frontOfTask) return false
+        if (procId != other.procId) return false
+        if (isTranslucent != other.isTranslucent) return false
+        if (orientation != other.orientation) return false
+        if (title != other.title) return false
+        if (token != other.token) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + state.hashCode()
+        result = 31 * result + frontOfTask.hashCode()
+        result = 31 * result + procId
+        result = 31 * result + isTranslucent.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/Configuration.kt b/libraries/flicker/src/android/tools/common/traces/wm/Configuration.kt
new file mode 100644
index 0000000..eeae490
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/Configuration.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the configuration of a WM container
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class Configuration
+private constructor(
+    @JsName("windowConfiguration") val windowConfiguration: WindowConfiguration? = null,
+    @JsName("densityDpi") val densityDpi: Int = 0,
+    @JsName("orientation") val orientation: Int = 0,
+    @JsName("screenHeightDp") val screenHeightDp: Int = 0,
+    @JsName("screenWidthDp") val screenWidthDp: Int = 0,
+    @JsName("smallestScreenWidthDp") val smallestScreenWidthDp: Int = 0,
+    @JsName("screenLayout") val screenLayout: Int = 0,
+    @JsName("uiMode") val uiMode: Int = 0
+) {
+    @JsName("isEmpty")
+    val isEmpty: Boolean
+        get() =
+            (windowConfiguration == null) &&
+                densityDpi == 0 &&
+                orientation == 0 &&
+                screenHeightDp == 0 &&
+                screenWidthDp == 0 &&
+                smallestScreenWidthDp == 0 &&
+                screenLayout == 0 &&
+                uiMode == 0
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Configuration) return false
+
+        if (windowConfiguration != other.windowConfiguration) return false
+        if (densityDpi != other.densityDpi) return false
+        if (orientation != other.orientation) return false
+        if (screenHeightDp != other.screenHeightDp) return false
+        if (screenWidthDp != other.screenWidthDp) return false
+        if (smallestScreenWidthDp != other.smallestScreenWidthDp) return false
+        if (screenLayout != other.screenLayout) return false
+        if (uiMode != other.uiMode) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = windowConfiguration?.hashCode() ?: 0
+        result = 31 * result + densityDpi
+        result = 31 * result + orientation
+        result = 31 * result + screenHeightDp
+        result = 31 * result + screenWidthDp
+        result = 31 * result + smallestScreenWidthDp
+        result = 31 * result + screenLayout
+        result = 31 * result + uiMode
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: Configuration
+            get() = withCache { Configuration() }
+
+        @JsName("from")
+        fun from(
+            windowConfiguration: WindowConfiguration?,
+            densityDpi: Int,
+            orientation: Int,
+            screenHeightDp: Int,
+            screenWidthDp: Int,
+            smallestScreenWidthDp: Int,
+            screenLayout: Int,
+            uiMode: Int
+        ): Configuration = withCache {
+            Configuration(
+                windowConfiguration,
+                densityDpi,
+                orientation,
+                screenHeightDp,
+                screenWidthDp,
+                smallestScreenWidthDp,
+                screenLayout,
+                uiMode
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/ConfigurationContainer.kt b/libraries/flicker/src/android/tools/common/traces/wm/ConfigurationContainer.kt
new file mode 100644
index 0000000..b29301f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/ConfigurationContainer.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the configuration of an element in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+open class ConfigurationContainer(
+    @JsName("overrideConfiguration") val overrideConfiguration: Configuration?,
+    @JsName("fullConfiguration") val fullConfiguration: Configuration?,
+    @JsName("mergedOverrideConfiguration") val mergedOverrideConfiguration: Configuration?
+) {
+    @JsName("windowingMode")
+    val windowingMode: Int
+        get() = fullConfiguration?.windowConfiguration?.windowingMode ?: 0
+
+    @JsName("activityType")
+    open val activityType: Int
+        get() = fullConfiguration?.windowConfiguration?.activityType ?: 0
+
+    @JsName("isEmpty")
+    open val isEmpty: Boolean
+        get() =
+            (overrideConfiguration?.isEmpty
+                ?: true) &&
+                (fullConfiguration?.isEmpty ?: true) &&
+                (mergedOverrideConfiguration?.isEmpty ?: true)
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ConfigurationContainer) return false
+
+        if (overrideConfiguration != other.overrideConfiguration) return false
+        if (fullConfiguration != other.fullConfiguration) return false
+        if (mergedOverrideConfiguration != other.mergedOverrideConfiguration) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = overrideConfiguration?.hashCode() ?: 0
+        result = 31 * result + (fullConfiguration?.hashCode() ?: 0)
+        result = 31 * result + (mergedOverrideConfiguration?.hashCode() ?: 0)
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/DisplayArea.kt b/libraries/flicker/src/android/tools/common/traces/wm/DisplayArea.kt
new file mode 100644
index 0000000..ea767af
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/DisplayArea.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a display area in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class DisplayArea(
+    @JsName("isTaskDisplayArea") val isTaskDisplayArea: Boolean,
+    windowContainer: WindowContainer
+) : WindowContainer(windowContainer) {
+    @JsName("activities")
+    val activities: Array<Activity>
+        get() =
+            if (isTaskDisplayArea) {
+                this.collectDescendants()
+            } else {
+                emptyArray()
+            }
+
+    /**
+     * @return if [componentMatcher] matches any activity
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("containsActivity")
+    fun containsActivity(componentMatcher: IComponentMatcher): Boolean {
+        return if (!isTaskDisplayArea) {
+            false
+        } else {
+            componentMatcher.activityMatchesAnyOf(activities)
+        }
+    }
+
+    override fun toString(): String {
+        return "${this::class.simpleName} {$token $title} isTaskArea=$isTaskDisplayArea"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is DisplayArea) return false
+
+        if (isTaskDisplayArea != other.isTaskDisplayArea) return false
+        if (isVisible != other.isVisible) return false
+        if (orientation != other.orientation) return false
+        if (title != other.title) return false
+        if (token != other.token) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + isTaskDisplayArea.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/DisplayContent.kt b/libraries/flicker/src/android/tools/common/traces/wm/DisplayContent.kt
new file mode 100644
index 0000000..68f544b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/DisplayContent.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+import kotlin.math.min
+
+/**
+ * Represents a display content in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class DisplayContent(
+    @JsName("id") val id: Int,
+    @JsName("focusedRootTaskId") val focusedRootTaskId: Int,
+    @JsName("resumedActivity") val resumedActivity: String,
+    @JsName("singleTaskInstance") val singleTaskInstance: Boolean,
+    @JsName("defaultPinnedStackBounds") val defaultPinnedStackBounds: Rect,
+    @JsName("pinnedStackMovementBounds") val pinnedStackMovementBounds: Rect,
+    @JsName("displayRect") val displayRect: Rect,
+    @JsName("appRect") val appRect: Rect,
+    @JsName("dpi") val dpi: Int,
+    @JsName("flags") val flags: Int,
+    @JsName("stableBounds") val stableBounds: Rect,
+    @JsName("surfaceSize") val surfaceSize: Int,
+    @JsName("focusedApp") val focusedApp: String,
+    @JsName("lastTransition") val lastTransition: String,
+    @JsName("appTransitionState") val appTransitionState: String,
+    @JsName("rotation") val rotation: Rotation,
+    @JsName("lastOrientation") val lastOrientation: Int,
+    @JsName("cutout") val cutout: DisplayCutout?,
+    windowContainer: WindowContainer
+) : WindowContainer(windowContainer) {
+    override val name: String = id.toString()
+    override val isVisible: Boolean = false
+
+    @JsName("isTablet")
+    val isTablet: Boolean
+        get() {
+            val smallestWidth =
+                dpiFromPx(min(displayRect.width.toFloat(), displayRect.height.toFloat()), dpi)
+            return smallestWidth >= TABLET_MIN_DPS
+        }
+
+    @JsName("rootTasks")
+    val rootTasks: Array<Task>
+        get() {
+            val tasks = this.collectDescendants<Task> { it.isRootTask }.toMutableList()
+            // TODO(b/149338177): figure out how CTS tests deal with organizer. For now,
+            //                    don't treat them as regular stacks
+            val rootOrganizedTasks = mutableListOf<Task>()
+            val reversedTaskList = tasks.reversed()
+            reversedTaskList.forEach { task ->
+                // Skip tasks created by an organizer
+                if (task.createdByOrganizer) {
+                    tasks.remove(task)
+                    rootOrganizedTasks.add(task)
+                }
+            }
+            // Add root tasks controlled by an organizer
+            rootOrganizedTasks.reversed().forEach { task ->
+                tasks.addAll(task.children.reversed().map { it as Task })
+            }
+
+            return tasks.toTypedArray()
+        }
+
+    /**
+     * @return if [componentMatcher] matches any activity
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("containsActivity")
+    fun containsActivity(componentMatcher: IComponentMatcher): Boolean =
+        rootTasks.any { it.containsActivity(componentMatcher) }
+
+    /**
+     * @return THe [DisplayArea] matching [componentMatcher], or null if none matches
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("getTaskDisplayArea")
+    fun getTaskDisplayArea(componentMatcher: IComponentMatcher): DisplayArea? {
+        val taskDisplayAreas =
+            this.collectDescendants<DisplayArea> { it.isTaskDisplayArea }
+                .filter { it.containsActivity(componentMatcher) }
+
+        if (taskDisplayAreas.size > 1) {
+            throw IllegalArgumentException(
+                "There must be exactly one activity among all TaskDisplayAreas."
+            )
+        }
+
+        return taskDisplayAreas.firstOrNull()
+    }
+
+    override fun toString(): String {
+        return "${this::class.simpleName} #$id: name=$title mDisplayRect=$displayRect " +
+            "mAppRect=$appRect mFlags=$flags"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is DisplayContent) return false
+        if (!super.equals(other)) return false
+
+        if (id != other.id) return false
+        if (focusedRootTaskId != other.focusedRootTaskId) return false
+        if (resumedActivity != other.resumedActivity) return false
+        if (defaultPinnedStackBounds != other.defaultPinnedStackBounds) return false
+        if (pinnedStackMovementBounds != other.pinnedStackMovementBounds) return false
+        if (stableBounds != other.stableBounds) return false
+        if (displayRect != other.displayRect) return false
+        if (appRect != other.appRect) return false
+        if (dpi != other.dpi) return false
+        if (flags != other.flags) return false
+        if (focusedApp != other.focusedApp) return false
+        if (lastTransition != other.lastTransition) return false
+        if (appTransitionState != other.appTransitionState) return false
+        if (rotation != other.rotation) return false
+        if (lastOrientation != other.lastOrientation) return false
+        if (cutout != other.cutout) return false
+        if (name != other.name) return false
+        if (singleTaskInstance != other.singleTaskInstance) return false
+        if (surfaceSize != other.surfaceSize) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + id
+        result = 31 * result + focusedRootTaskId
+        result = 31 * result + resumedActivity.hashCode()
+        result = 31 * result + singleTaskInstance.hashCode()
+        result = 31 * result + defaultPinnedStackBounds.hashCode()
+        result = 31 * result + pinnedStackMovementBounds.hashCode()
+        result = 31 * result + displayRect.hashCode()
+        result = 31 * result + appRect.hashCode()
+        result = 31 * result + dpi
+        result = 31 * result + flags
+        result = 31 * result + stableBounds.hashCode()
+        result = 31 * result + surfaceSize
+        result = 31 * result + focusedApp.hashCode()
+        result = 31 * result + lastTransition.hashCode()
+        result = 31 * result + appTransitionState.hashCode()
+        result = 31 * result + rotation.value
+        result = 31 * result + lastOrientation
+        result = 31 * result + cutout.hashCode()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + isVisible.hashCode()
+        return result
+    }
+
+    companion object {
+        /** From [android.util.DisplayMetrics] */
+        @JsName("DENSITY_DEFAULT") private const val DENSITY_DEFAULT = 160f
+        /** From [com.android.systemui.shared.recents.utilities.Utilities] */
+        @JsName("TABLET_MIN_DPS") private const val TABLET_MIN_DPS = 600f
+
+        @JsName("dpiFromPx")
+        private fun dpiFromPx(size: Float, densityDpi: Int): Float {
+            val densityRatio: Float = densityDpi.toFloat() / DENSITY_DEFAULT
+            return size / densityRatio
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/DisplayCutout.kt b/libraries/flicker/src/android/tools/common/traces/wm/DisplayCutout.kt
new file mode 100644
index 0000000..9d8e882
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/DisplayCutout.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.Insets
+import android.tools.common.datatypes.Rect
+import kotlin.js.JsExport
+
+/** Representation of a display cutout from a WM trace */
+@JsExport
+data class DisplayCutout(
+    val insets: Insets,
+    val boundLeft: Rect,
+    val boundTop: Rect,
+    val boundRight: Rect,
+    val boundBottom: Rect,
+    val waterfallInsets: Insets
+)
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/KeyguardControllerState.kt b/libraries/flicker/src/android/tools/common/traces/wm/KeyguardControllerState.kt
new file mode 100644
index 0000000..e4be36c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/KeyguardControllerState.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the keyguard controller in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class KeyguardControllerState
+private constructor(
+    val isAodShowing: Boolean,
+    val isKeyguardShowing: Boolean,
+    val keyguardOccludedStates: Map<Int, Boolean>
+) {
+    fun isKeyguardOccluded(displayId: Int): Boolean = keyguardOccludedStates[displayId] ?: false
+
+    override fun toString(): String {
+        return "KeyguardControllerState: {aod=$isAodShowing keyguard=$isKeyguardShowing}"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is KeyguardControllerState) return false
+
+        if (isAodShowing != other.isAodShowing) return false
+        if (isKeyguardShowing != other.isKeyguardShowing) return false
+        if (keyguardOccludedStates != other.keyguardOccludedStates) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = isAodShowing.hashCode()
+        result = 31 * result + isKeyguardShowing.hashCode()
+        result = 31 * result + keyguardOccludedStates.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("from")
+        fun from(
+            isAodShowing: Boolean,
+            isKeyguardShowing: Boolean,
+            keyguardOccludedStates: Map<Int, Boolean>
+        ): KeyguardControllerState = withCache {
+            KeyguardControllerState(isAodShowing, isKeyguardShowing, keyguardOccludedStates)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/RootWindowContainer.kt b/libraries/flicker/src/android/tools/common/traces/wm/RootWindowContainer.kt
new file mode 100644
index 0000000..959e66e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/RootWindowContainer.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+
+/**
+ * Represents the root window container in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class RootWindowContainer(windowContainer: WindowContainer) : WindowContainer(windowContainer) {
+    override fun toString(): String {
+        return "${this::class.simpleName}: {$token $title}"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/Task.kt b/libraries/flicker/src/android/tools/common/traces/wm/Task.kt
new file mode 100644
index 0000000..6429cbe
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/Task.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a task in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class Task(
+    override val activityType: Int,
+    override val isFullscreen: Boolean,
+    override val bounds: Rect,
+    @JsName("taskId") val taskId: Int,
+    @JsName("rootTaskId") val rootTaskId: Int,
+    @JsName("displayId") val displayId: Int,
+    @JsName("lastNonFullscreenBounds") val lastNonFullscreenBounds: Rect,
+    @JsName("realActivity") val realActivity: String,
+    @JsName("origActivity") val origActivity: String,
+    @JsName("resizeMode") val resizeMode: Int,
+    @JsName("_resumedActivity") private val _resumedActivity: String,
+    @JsName("animatingBounds") var animatingBounds: Boolean,
+    @JsName("surfaceWidth") val surfaceWidth: Int,
+    @JsName("surfaceHeight") val surfaceHeight: Int,
+    @JsName("createdByOrganizer") val createdByOrganizer: Boolean,
+    @JsName("minWidth") val minWidth: Int,
+    @JsName("minHeight") val minHeight: Int,
+    windowContainer: WindowContainer
+) : WindowContainer(windowContainer) {
+    override val isVisible: Boolean = false
+    override val name: String = taskId.toString()
+    override val isEmpty: Boolean
+        get() = tasks.isEmpty() && activities.isEmpty()
+    override val stableId: String
+        get() = "${this::class.simpleName} $token $title $taskId"
+
+    @JsName("isRootTask")
+    val isRootTask: Boolean
+        get() = taskId == rootTaskId
+    @JsName("tasks")
+    val tasks: Array<Task>
+        get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
+    @JsName("taskFragments")
+    val taskFragments: Array<TaskFragment>
+        get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
+    @JsName("activities")
+    val activities: Array<Activity>
+        get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
+    /** The top task in the stack. */
+    // NOTE: Unlike the WindowManager internals, we dump the state from top to bottom,
+    //       so the indices are inverted
+    @JsName("topTask")
+    val topTask: Task?
+        get() = tasks.firstOrNull()
+    @JsName("resumedActivities")
+    val resumedActivities: Array<String>
+        get() {
+            val result = mutableSetOf<String>()
+            if (this._resumedActivity.isNotEmpty()) {
+                result.add(this._resumedActivity)
+            }
+            val activitiesInChildren =
+                this.tasks.flatMap { it.resumedActivities.toList() }.filter { it.isNotEmpty() }
+            result.addAll(activitiesInChildren)
+            return result.toTypedArray()
+        }
+
+    /** @return The first [Task] matching [predicate], or null otherwise */
+    @JsName("getTask")
+    fun getTask(predicate: (Task) -> Boolean) =
+        tasks.firstOrNull { predicate(it) } ?: if (predicate(this)) this else null
+
+    /** @return the first [Activity] matching [predicate], or null otherwise */
+    @JsName("getActivityByPredicate")
+    internal fun getActivity(predicate: (Activity) -> Boolean): Activity? {
+        var activity: Activity? = activities.firstOrNull { predicate(it) }
+        if (activity != null) {
+            return activity
+        }
+        for (task in tasks) {
+            activity = task.getActivity(predicate)
+            if (activity != null) {
+                return activity
+            }
+        }
+        for (taskFragment in taskFragments) {
+            activity = taskFragment.getActivity(predicate)
+            if (activity != null) {
+                return activity
+            }
+        }
+        return null
+    }
+
+    /**
+     * @return the first [Activity] matching [componentMatcher], or null otherwise
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("getActivity")
+    fun getActivity(componentMatcher: IComponentMatcher): Activity? = getActivity { activity ->
+        componentMatcher.activityMatchesAnyOf(activity)
+    }
+
+    /**
+     * @return if any activity matches [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("containsActivity")
+    fun containsActivity(componentMatcher: IComponentMatcher) =
+        getActivity(componentMatcher) != null
+
+    override fun toString(): String {
+        return "${this::class.simpleName}: {$token $title} id=$taskId bounds=$bounds"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Task) return false
+
+        if (activityType != other.activityType) return false
+        if (isFullscreen != other.isFullscreen) return false
+        if (bounds != other.bounds) return false
+        if (taskId != other.taskId) return false
+        if (rootTaskId != other.rootTaskId) return false
+        if (displayId != other.displayId) return false
+        if (realActivity != other.realActivity) return false
+        if (resizeMode != other.resizeMode) return false
+        if (minWidth != other.minWidth) return false
+        if (minHeight != other.minHeight) return false
+        if (name != other.name) return false
+        if (orientation != other.orientation) return false
+        if (title != other.title) return false
+        if (token != other.token) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + activityType
+        result = 31 * result + isFullscreen.hashCode()
+        result = 31 * result + bounds.hashCode()
+        result = 31 * result + taskId
+        result = 31 * result + rootTaskId
+        result = 31 * result + displayId
+        result = 31 * result + lastNonFullscreenBounds.hashCode()
+        result = 31 * result + realActivity.hashCode()
+        result = 31 * result + origActivity.hashCode()
+        result = 31 * result + resizeMode
+        result = 31 * result + _resumedActivity.hashCode()
+        result = 31 * result + animatingBounds.hashCode()
+        result = 31 * result + surfaceWidth
+        result = 31 * result + surfaceHeight
+        result = 31 * result + createdByOrganizer.hashCode()
+        result = 31 * result + minWidth
+        result = 31 * result + minHeight
+        result = 31 * result + isVisible.hashCode()
+        result = 31 * result + name.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TaskFragment.kt b/libraries/flicker/src/android/tools/common/traces/wm/TaskFragment.kt
new file mode 100644
index 0000000..62b880d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TaskFragment.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a task fragment in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class TaskFragment(
+    override val activityType: Int,
+    @JsName("displayId") val displayId: Int,
+    @JsName("minWidth") val minWidth: Int,
+    @JsName("minHeight") val minHeight: Int,
+    windowContainer: WindowContainer
+) : WindowContainer(windowContainer) {
+    @JsName("tasks")
+    val tasks: Array<Task>
+        get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
+    @JsName("taskFragments")
+    val taskFragments: Array<TaskFragment>
+        get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
+    @JsName("activities")
+    val activities: Array<Activity>
+        get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
+
+    @JsName("getActivity")
+    fun getActivity(predicate: (Activity) -> Boolean): Activity? {
+        var activity: Activity? = activities.firstOrNull { predicate(it) }
+        if (activity != null) {
+            return activity
+        }
+        for (task in tasks) {
+            activity = task.getActivity(predicate)
+            if (activity != null) {
+                return activity
+            }
+        }
+        for (taskFragment in taskFragments) {
+            activity = taskFragment.getActivity(predicate)
+            if (activity != null) {
+                return activity
+            }
+        }
+        return null
+    }
+
+    override fun toString(): String {
+        return "${this::class.simpleName}: {$token $title} bounds=$bounds"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TaskFragment) return false
+
+        if (activityType != other.activityType) return false
+        if (displayId != other.displayId) return false
+        if (minWidth != other.minWidth) return false
+        if (minHeight != other.minHeight) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + activityType
+        result = 31 * result + displayId
+        result = 31 * result + minWidth
+        result = 31 * result + minHeight
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitMode.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitMode.kt
new file mode 100644
index 0000000..ac088bd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitMode.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsName
+
+enum class TransitMode {
+    TRANSIT_NONE,
+    TRANSIT_OPEN,
+    TRANSIT_CLOSE,
+    TRANSIT_TO_FRONT,
+    TRANSIT_TO_BACK,
+    TRANSIT_CHANGE;
+
+    companion object {
+        @JsName("fromInt") fun fromInt(value: Int) = values().first { it.ordinal == value }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/Transition.kt b/libraries/flicker/src/android/tools/common/traces/wm/Transition.kt
new file mode 100644
index 0000000..12ae0be
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/Transition.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+import android.tools.common.traces.surfaceflinger.Transaction
+import kotlin.js.JsName
+
+class Transition(
+    @JsName("start") val start: Timestamp,
+    @JsName("sendTime") val sendTime: Timestamp,
+    @JsName("startTransactionId") val startTransactionId: Long,
+    @JsName("finishTransactionId") val finishTransactionId: Long,
+    @JsName("changes") val changes: List<TransitionChange>,
+    @JsName("played") val played: Boolean,
+    @JsName("aborted") val aborted: Boolean
+) : ITraceEntry {
+    override val timestamp = start
+
+    @JsName("startTransaction") val startTransaction: Transaction? = null // TODO: Get
+    @JsName("finishTransaction") val finishTransaction: Transaction? = null // TODO: Get
+
+    @JsName("isIncomplete")
+    val isIncomplete: Boolean
+        get() = !played || aborted
+
+    override fun toString(): String =
+        "Transition#${hashCode()}" +
+            "(\naborted=$aborted,\nstart=$start,\nsendTime=$sendTime,\n" +
+            "startTransaction=$startTransaction,\nfinishTransaction=$finishTransaction,\n" +
+            "changes=[\n${changes.joinToString(",\n").prependIndent()}\n])"
+
+    companion object {
+        enum class Type(val value: Int) {
+            UNDEFINED(-1),
+            NONE(0),
+            OPEN(1),
+            CLOSE(2),
+            TO_FRONT(3),
+            TO_BACK(4),
+            RELAUNCH(5),
+            CHANGE(6),
+            KEYGUARD_GOING_AWAY(7),
+            KEYGUARD_OCCLUDE(8),
+            KEYGUARD_UNOCCLUDE(9),
+            PIP(10),
+            WAKE(11),
+            // START OF CUSTOM TYPES
+            FIRST_CUSTOM(12); // TODO: Add custom types we know about
+
+            companion object {
+                @JsName("fromInt") fun fromInt(value: Int) = values().first { it.value == value }
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitionChange.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitionChange.kt
new file mode 100644
index 0000000..1fb51c5
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitionChange.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.traces.wm.Transition.Companion.Type
+import kotlin.js.JsName
+
+class TransitionChange(
+    @JsName("transitMode") val transitMode: Type,
+    @JsName("layerId") val layerId: Int,
+    @JsName("windowId") val windowId: Int,
+    @JsName("windowingMode") val windowingMode: WindowingMode
+) {
+
+    override fun toString(): String {
+        return "TransitionChange(" +
+            "transitMode=$transitMode, " +
+            "layerId=$layerId, " +
+            "windowId=$windowId, " +
+            "windowingMode=$windowingMode" +
+            ")"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitionInfo.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitionInfo.kt
new file mode 100644
index 0000000..5970032
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitionInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.traces.wm
+
+data class TransitionInfo(val transitionId: Int, val changes: List<Change>) {
+    data class Change(
+        val layerId: Int,
+        val transitMode: TransitMode,
+    )
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitionState.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitionState.kt
new file mode 100644
index 0000000..2d49a23
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitionState.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.Timestamp
+import kotlin.js.JsName
+
+data class TransitionState(
+    @JsName("id") val id: Int,
+    @JsName("type") val type: Transition.Companion.Type,
+    @JsName("timestamp") val timestamp: Timestamp,
+    @JsName("state") val state: State,
+    @JsName("flags") val flags: Int,
+    @JsName("changes") val changes: List<TransitionChange>,
+    @JsName("startTransactionId") val startTransactionId: Long,
+    @JsName("finishTransactionId") val finishTransactionId: Long
+) {
+    companion object {
+        enum class State(val value: Int) {
+            PENDING(-1),
+            COLLECTING(0),
+            STARTED(1),
+            PLAYING(2),
+            ABORT(3),
+            FINISHED(4);
+
+            companion object {
+                @JsName("fromInt") fun fromInt(value: Int) = values().first { it.value == value }
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitionTraceEntry.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitionTraceEntry.kt
new file mode 100644
index 0000000..2290433
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitionTraceEntry.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.traces.wm
+
+class TransitionTraceEntry
+private constructor(
+    val transitionState: TransitionState? = null,
+    val transitionInfo: TransitionInfo? = null
+) {
+    fun hasTransitionState(): Boolean = transitionState != null
+    fun hasTransitionInfo(): Boolean = transitionInfo != null
+
+    constructor(transitionState: TransitionState) : this(transitionState, null)
+
+    constructor(transitionInfo: TransitionInfo) : this(null, transitionInfo)
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/TransitionsTrace.kt b/libraries/flicker/src/android/tools/common/traces/wm/TransitionsTrace.kt
new file mode 100644
index 0000000..e3ed6c8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/TransitionsTrace.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import kotlin.js.JsName
+import kotlin.text.StringBuilder
+
+data class TransitionsTrace(override val entries: Array<Transition>) : ITrace<Transition> {
+    constructor(entry: Transition) : this(arrayOf(entry))
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TransitionsTrace) return false
+
+        if (!entries.contentEquals(other.entries)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return entries.contentHashCode()
+    }
+
+    @JsName("prettyPrint")
+    fun prettyPrint(): String {
+        val sb = StringBuilder("TransitionTrace(")
+
+        for (transition in entries) {
+            sb.append("\n\t- ").append(transition)
+        }
+        if (entries.isEmpty()) {
+            sb.append("EMPTY)")
+        } else {
+            sb.append("\n)")
+        }
+        return sb.toString()
+    }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): TransitionsTrace {
+        require(startTimestamp.hasElapsedTimestamp && endTimestamp.hasElapsedTimestamp)
+        return sliceElapsed(startTimestamp.elapsedNanos, endTimestamp.elapsedNanos)
+    }
+
+    private fun sliceElapsed(from: Long, to: Long): TransitionsTrace {
+        return TransitionsTrace(
+            this.entries
+                .dropWhile { it.sendTime.elapsedNanos < from }
+                .dropLastWhile { it.start.elapsedNanos > to }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowConfiguration.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowConfiguration.kt
new file mode 100644
index 0000000..7439fa4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowConfiguration.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the configuration of a WM window
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+open class WindowConfiguration(
+    @JsName("appBounds") val appBounds: Rect = Rect.EMPTY,
+    @JsName("bounds") val bounds: Rect = Rect.EMPTY,
+    @JsName("maxBounds") val maxBounds: Rect = Rect.EMPTY,
+    @JsName("windowingMode") val windowingMode: Int = 0,
+    @JsName("activityType") val activityType: Int = 0
+) {
+    @JsName("isEmpty")
+    val isEmpty: Boolean
+        get() =
+            appBounds.isEmpty &&
+                bounds.isEmpty &&
+                maxBounds.isEmpty &&
+                windowingMode == 0 &&
+                activityType == 0
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowConfiguration) return false
+
+        if (windowingMode != other.windowingMode) return false
+        if (activityType != other.activityType) return false
+        if (appBounds != other.appBounds) return false
+        if (bounds != other.bounds) return false
+        if (maxBounds != other.maxBounds) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = windowingMode
+        result = 31 * result + activityType
+        result = 31 * result + appBounds.hashCode()
+        result = 31 * result + bounds.hashCode()
+        result = 31 * result + maxBounds.hashCode()
+        return result
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: WindowConfiguration
+            get() = withCache { WindowConfiguration() }
+        @JsName("from")
+        fun from(
+            appBounds: Rect?,
+            bounds: Rect?,
+            maxBounds: Rect?,
+            windowingMode: Int,
+            activityType: Int
+        ): WindowConfiguration = withCache {
+            WindowConfiguration(
+                appBounds ?: Rect.EMPTY,
+                bounds ?: Rect.EMPTY,
+                maxBounds ?: Rect.EMPTY,
+                windowingMode,
+                activityType
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowContainer.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowContainer.kt
new file mode 100644
index 0000000..01e6938
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowContainer.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.Rect
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents WindowContainer classes such as DisplayContent.WindowContainers and
+ * DisplayContent.NonAppWindowContainers. This can be expanded into a specific class if we need
+ * track and assert some state in the future.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+open class WindowContainer
+constructor(
+    @JsName("title") val title: String,
+    @JsName("token") val token: String,
+    @JsName("orientation") val orientation: Int,
+    @JsName("layerId") val layerId: Int,
+    _isVisible: Boolean,
+    configurationContainer: ConfigurationContainer,
+    @JsName("children") val children: Array<WindowContainer>,
+    @JsName("computedZ") val computedZ: Int
+) :
+    ConfigurationContainer(
+        configurationContainer.overrideConfiguration,
+        configurationContainer.fullConfiguration,
+        configurationContainer.mergedOverrideConfiguration
+    ) {
+    protected constructor(
+        windowContainer: WindowContainer,
+        titleOverride: String? = null,
+        isVisibleOverride: Boolean? = null
+    ) : this(
+        titleOverride ?: windowContainer.title,
+        windowContainer.token,
+        windowContainer.orientation,
+        windowContainer.layerId,
+        isVisibleOverride ?: windowContainer.isVisible,
+        windowContainer,
+        windowContainer.children,
+        windowContainer.computedZ
+    )
+
+    @JsName("parent")
+    var parent: WindowContainer? = null
+        private set
+
+    init {
+        children.forEach { it.parent = this }
+    }
+
+    @JsName("isVisible") open val isVisible: Boolean = _isVisible
+    @JsName("name") open val name: String = title
+    @JsName("stableId")
+    open val stableId: String
+        get() = "${this::class.simpleName} $token $title"
+    @JsName("isFullscreen") open val isFullscreen: Boolean = false
+    @JsName("bounds") open val bounds: Rect = Rect.EMPTY
+
+    internal fun traverseTopDown(): List<WindowContainer> {
+        val traverseList = mutableListOf(this)
+
+        this.children.reversed().forEach { childLayer ->
+            traverseList.addAll(childLayer.traverseTopDown())
+        }
+
+        return traverseList
+    }
+
+    /**
+     * For a given WindowContainer, traverse down the hierarchy and collect all children of type [T]
+     * if the child passes the test [predicate].
+     *
+     * @param predicate Filter function
+     */
+    internal inline fun <reified T : WindowContainer> collectDescendants(
+        predicate: (T) -> Boolean = { true }
+    ): Array<T> {
+        val traverseList = traverseTopDown()
+
+        return traverseList.filterIsInstance<T>().filter { predicate(it) }.toTypedArray()
+    }
+
+    override fun toString(): String {
+        if (
+            this.title.isEmpty() ||
+                listOf("WindowContainer", "Task").any { it.contains(this.title) }
+        ) {
+            return ""
+        }
+
+        return "$${removeRedundancyInName(this.title)}@${this.token}"
+    }
+
+    private fun removeRedundancyInName(name: String): String {
+        if (!name.contains('/')) {
+            return name
+        }
+
+        val split = name.split('/')
+        val pkg = split[0]
+        var clazz = split.slice(1..split.lastIndex).joinToString("/")
+
+        if (clazz.startsWith("$pkg.")) {
+            clazz = clazz.slice(pkg.length + 1..clazz.lastIndex)
+
+            return "$pkg/$clazz"
+        }
+
+        return name
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowContainer) return false
+
+        if (title != other.title) return false
+        if (token != other.token) return false
+        if (orientation != other.orientation) return false
+        if (isVisible != other.isVisible) return false
+        if (name != other.name) return false
+        if (isFullscreen != other.isFullscreen) return false
+        if (bounds != other.bounds) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = title.hashCode()
+        result = 31 * result + token.hashCode()
+        result = 31 * result + orientation
+        result = 31 * result + children.contentHashCode()
+        result = 31 * result + isVisible.hashCode()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + isFullscreen.hashCode()
+        result = 31 * result + bounds.hashCode()
+        return result
+    }
+
+    override val isEmpty: Boolean
+        get() = super.isEmpty && title.isEmpty() && token.isEmpty()
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowLayoutParams.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowLayoutParams.kt
new file mode 100644
index 0000000..2cff57e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowLayoutParams.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the attributes of a WindowState in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class WindowLayoutParams
+private constructor(
+    @JsName("type") val type: Int = 0,
+    @JsName("x") val x: Int = 0,
+    @JsName("y") val y: Int = 0,
+    @JsName("width") val width: Int = 0,
+    @JsName("height") val height: Int = 0,
+    @JsName("horizontalMargin") val horizontalMargin: Float = 0f,
+    @JsName("verticalMargin") val verticalMargin: Float = 0f,
+    @JsName("gravity") val gravity: Int = 0,
+    @JsName("softInputMode") val softInputMode: Int = 0,
+    @JsName("format") val format: Int = 0,
+    @JsName("windowAnimations") val windowAnimations: Int = 0,
+    @JsName("alpha") val alpha: Float = 0f,
+    @JsName("screenBrightness") val screenBrightness: Float = 0f,
+    @JsName("buttonBrightness") val buttonBrightness: Float = 0f,
+    @JsName("rotationAnimation") val rotationAnimation: Int = 0,
+    @JsName("preferredRefreshRate") val preferredRefreshRate: Float = 0f,
+    @JsName("preferredDisplayModeId") val preferredDisplayModeId: Int = 0,
+    @JsName("hasSystemUiListeners") val hasSystemUiListeners: Boolean = false,
+    @JsName("inputFeatureFlags") val inputFeatureFlags: Int = 0,
+    @JsName("userActivityTimeout") val userActivityTimeout: Long = 0L,
+    @JsName("colorMode") val colorMode: Int = 0,
+    @JsName("flags") val flags: Int = 0,
+    @JsName("privateFlags") val privateFlags: Int = 0,
+    @JsName("systemUiVisibilityFlags") val systemUiVisibilityFlags: Int = 0,
+    @JsName("subtreeSystemUiVisibilityFlags") val subtreeSystemUiVisibilityFlags: Int = 0,
+    @JsName("appearance") val appearance: Int = 0,
+    @JsName("behavior") val behavior: Int = 0,
+    @JsName("fitInsetsTypes") val fitInsetsTypes: Int = 0,
+    @JsName("fitInsetsSides") val fitInsetsSides: Int = 0,
+    @JsName("fitIgnoreVisibility") val fitIgnoreVisibility: Boolean = false
+) {
+    @JsName("isValidNavBarType") val isValidNavBarType: Boolean = this.type == TYPE_NAVIGATION_BAR
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowLayoutParams) return false
+
+        if (type != other.type) return false
+        if (x != other.x) return false
+        if (y != other.y) return false
+        if (width != other.width) return false
+        if (height != other.height) return false
+        if (horizontalMargin != other.horizontalMargin) return false
+        if (verticalMargin != other.verticalMargin) return false
+        if (gravity != other.gravity) return false
+        if (softInputMode != other.softInputMode) return false
+        if (format != other.format) return false
+        if (windowAnimations != other.windowAnimations) return false
+        if (alpha != other.alpha) return false
+        if (screenBrightness != other.screenBrightness) return false
+        if (buttonBrightness != other.buttonBrightness) return false
+        if (rotationAnimation != other.rotationAnimation) return false
+        if (preferredRefreshRate != other.preferredRefreshRate) return false
+        if (preferredDisplayModeId != other.preferredDisplayModeId) return false
+        if (hasSystemUiListeners != other.hasSystemUiListeners) return false
+        if (inputFeatureFlags != other.inputFeatureFlags) return false
+        if (userActivityTimeout != other.userActivityTimeout) return false
+        if (colorMode != other.colorMode) return false
+        if (flags != other.flags) return false
+        if (privateFlags != other.privateFlags) return false
+        if (systemUiVisibilityFlags != other.systemUiVisibilityFlags) return false
+        if (subtreeSystemUiVisibilityFlags != other.subtreeSystemUiVisibilityFlags) return false
+        if (appearance != other.appearance) return false
+        if (behavior != other.behavior) return false
+        if (fitInsetsTypes != other.fitInsetsTypes) return false
+        if (fitInsetsSides != other.fitInsetsSides) return false
+        if (fitIgnoreVisibility != other.fitIgnoreVisibility) return false
+        if (isValidNavBarType != other.isValidNavBarType) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = type
+        result = 31 * result + x
+        result = 31 * result + y
+        result = 31 * result + width
+        result = 31 * result + height
+        result = 31 * result + horizontalMargin.hashCode()
+        result = 31 * result + verticalMargin.hashCode()
+        result = 31 * result + gravity
+        result = 31 * result + softInputMode
+        result = 31 * result + format
+        result = 31 * result + windowAnimations
+        result = 31 * result + alpha.hashCode()
+        result = 31 * result + screenBrightness.hashCode()
+        result = 31 * result + buttonBrightness.hashCode()
+        result = 31 * result + rotationAnimation
+        result = 31 * result + preferredRefreshRate.hashCode()
+        result = 31 * result + preferredDisplayModeId
+        result = 31 * result + hasSystemUiListeners.hashCode()
+        result = 31 * result + inputFeatureFlags
+        result = 31 * result + userActivityTimeout.hashCode()
+        result = 31 * result + colorMode
+        result = 31 * result + flags
+        result = 31 * result + privateFlags
+        result = 31 * result + systemUiVisibilityFlags
+        result = 31 * result + subtreeSystemUiVisibilityFlags
+        result = 31 * result + appearance
+        result = 31 * result + behavior
+        result = 31 * result + fitInsetsTypes
+        result = 31 * result + fitInsetsSides
+        result = 31 * result + fitIgnoreVisibility.hashCode()
+        result = 31 * result + isValidNavBarType.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "WindowLayoutParams(type=$type, x=$x, y=$y, width=$width, height=$height, " +
+            "horizontalMargin=$horizontalMargin, verticalMargin=$verticalMargin, " +
+            "gravity=$gravity, softInputMode=$softInputMode, format=$format, " +
+            "windowAnimations=$windowAnimations, alpha=$alpha, " +
+            "screenBrightness=$screenBrightness, buttonBrightness=$buttonBrightness, " +
+            "rotationAnimation=$rotationAnimation, preferredRefreshRate=$preferredRefreshRate, " +
+            "preferredDisplayModeId=$preferredDisplayModeId, " +
+            "hasSystemUiListeners=$hasSystemUiListeners, inputFeatureFlags=$inputFeatureFlags, " +
+            "userActivityTimeout=$userActivityTimeout, colorMode=$colorMode, flags=$flags, " +
+            "privateFlags=$privateFlags, systemUiVisibilityFlags=$systemUiVisibilityFlags, " +
+            "subtreeSystemUiVisibilityFlags=$subtreeSystemUiVisibilityFlags, " +
+            "appearance=$appearance, behavior=$behavior, fitInsetsTypes=$fitInsetsTypes, " +
+            "fitInsetsSides=$fitInsetsSides, fitIgnoreVisibility=$fitIgnoreVisibility, " +
+            "isValidNavBarType=$isValidNavBarType)"
+    }
+
+    companion object {
+        val EMPTY: WindowLayoutParams
+            get() = withCache { WindowLayoutParams() }
+        /** @see WindowManager.LayoutParams */
+        private const val TYPE_NAVIGATION_BAR = 2019
+
+        @JsName("from")
+        fun from(
+            type: Int,
+            x: Int,
+            y: Int,
+            width: Int,
+            height: Int,
+            horizontalMargin: Float,
+            verticalMargin: Float,
+            gravity: Int,
+            softInputMode: Int,
+            format: Int,
+            windowAnimations: Int,
+            alpha: Float,
+            screenBrightness: Float,
+            buttonBrightness: Float,
+            rotationAnimation: Int,
+            preferredRefreshRate: Float,
+            preferredDisplayModeId: Int,
+            hasSystemUiListeners: Boolean,
+            inputFeatureFlags: Int,
+            userActivityTimeout: Long,
+            colorMode: Int,
+            flags: Int,
+            privateFlags: Int,
+            systemUiVisibilityFlags: Int,
+            subtreeSystemUiVisibilityFlags: Int,
+            appearance: Int,
+            behavior: Int,
+            fitInsetsTypes: Int,
+            fitInsetsSides: Int,
+            fitIgnoreVisibility: Boolean
+        ): WindowLayoutParams = withCache {
+            WindowLayoutParams(
+                type,
+                x,
+                y,
+                width,
+                height,
+                horizontalMargin,
+                verticalMargin,
+                gravity,
+                softInputMode,
+                format,
+                windowAnimations,
+                alpha,
+                screenBrightness,
+                buttonBrightness,
+                rotationAnimation,
+                preferredRefreshRate,
+                preferredDisplayModeId,
+                hasSystemUiListeners,
+                inputFeatureFlags,
+                userActivityTimeout,
+                colorMode,
+                flags,
+                privateFlags,
+                systemUiVisibilityFlags,
+                subtreeSystemUiVisibilityFlags,
+                appearance,
+                behavior,
+                fitInsetsTypes,
+                fitInsetsSides,
+                fitIgnoreVisibility
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerPolicy.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerPolicy.kt
new file mode 100644
index 0000000..bb9026a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerPolicy.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.Rotation
+import android.tools.common.withCache
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents the requested policy of a WM container
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class WindowManagerPolicy
+private constructor(
+    @JsName("focusedAppToken") val focusedAppToken: String = "",
+    @JsName("forceStatusBar") val forceStatusBar: Boolean = false,
+    @JsName("forceStatusBarFromKeyguard") val forceStatusBarFromKeyguard: Boolean = false,
+    @JsName("keyguardDrawComplete") val keyguardDrawComplete: Boolean = false,
+    @JsName("keyguardOccluded") val keyguardOccluded: Boolean = false,
+    @JsName("keyguardOccludedChanged") val keyguardOccludedChanged: Boolean = false,
+    @JsName("keyguardOccludedPending") val keyguardOccludedPending: Boolean = false,
+    @JsName("lastSystemUiFlags") val lastSystemUiFlags: Int = 0,
+    @JsName("orientation") val orientation: Int = 0,
+    @JsName("rotation") val rotation: Rotation = Rotation.ROTATION_0,
+    @JsName("rotationMode") val rotationMode: Int = 0,
+    @JsName("screenOnFully") val screenOnFully: Boolean = false,
+    @JsName("windowManagerDrawComplete") val windowManagerDrawComplete: Boolean = false
+) {
+    @JsName("isOrientationNoSensor")
+    val isOrientationNoSensor: Boolean
+        get() = orientation == SCREEN_ORIENTATION_NOSENSOR
+
+    @JsName("isFixedOrientation")
+    val isFixedOrientation: Boolean
+        get() =
+            isFixedOrientationLandscape ||
+                isFixedOrientationPortrait ||
+                orientation == SCREEN_ORIENTATION_LOCKED
+
+    @JsName("isFixedOrientationLandscape")
+    private val isFixedOrientationLandscape
+        get() =
+            orientation == SCREEN_ORIENTATION_LANDSCAPE ||
+                orientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE ||
+                orientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE ||
+                orientation == SCREEN_ORIENTATION_USER_LANDSCAPE
+
+    @JsName("isFixedOrientationPortrait")
+    private val isFixedOrientationPortrait
+        get() =
+            orientation == SCREEN_ORIENTATION_PORTRAIT ||
+                orientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT ||
+                orientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
+                orientation == SCREEN_ORIENTATION_USER_PORTRAIT
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowManagerPolicy) return false
+
+        if (focusedAppToken != other.focusedAppToken) return false
+        if (forceStatusBar != other.forceStatusBar) return false
+        if (forceStatusBarFromKeyguard != other.forceStatusBarFromKeyguard) return false
+        if (keyguardDrawComplete != other.keyguardDrawComplete) return false
+        if (keyguardOccluded != other.keyguardOccluded) return false
+        if (keyguardOccludedChanged != other.keyguardOccludedChanged) return false
+        if (keyguardOccludedPending != other.keyguardOccludedPending) return false
+        if (lastSystemUiFlags != other.lastSystemUiFlags) return false
+        if (orientation != other.orientation) return false
+        if (rotation != other.rotation) return false
+        if (rotationMode != other.rotationMode) return false
+        if (screenOnFully != other.screenOnFully) return false
+        if (windowManagerDrawComplete != other.windowManagerDrawComplete) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = focusedAppToken.hashCode()
+        result = 31 * result + forceStatusBar.hashCode()
+        result = 31 * result + forceStatusBarFromKeyguard.hashCode()
+        result = 31 * result + keyguardDrawComplete.hashCode()
+        result = 31 * result + keyguardOccluded.hashCode()
+        result = 31 * result + keyguardOccludedChanged.hashCode()
+        result = 31 * result + keyguardOccludedPending.hashCode()
+        result = 31 * result + lastSystemUiFlags
+        result = 31 * result + orientation
+        result = 31 * result + rotation.hashCode()
+        result = 31 * result + rotationMode
+        result = 31 * result + screenOnFully.hashCode()
+        result = 31 * result + windowManagerDrawComplete.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "${this::class.simpleName} (focusedAppToken='$focusedAppToken', " +
+            "forceStatusBar=$forceStatusBar, " +
+            "forceStatusBarFromKeyguard=$forceStatusBarFromKeyguard, " +
+            "keyguardDrawComplete=$keyguardDrawComplete, keyguardOccluded=$keyguardOccluded, " +
+            "keyguardOccludedChanged=$keyguardOccludedChanged, " +
+            "keyguardOccludedPending=$keyguardOccludedPending, " +
+            "lastSystemUiFlags=$lastSystemUiFlags, orientation=$orientation, " +
+            "rotation=$rotation, rotationMode=$rotationMode, " +
+            "screenOnFully=$screenOnFully, " +
+            "windowManagerDrawComplete=$windowManagerDrawComplete)"
+    }
+
+    companion object {
+        @JsName("EMPTY")
+        val EMPTY: WindowManagerPolicy
+            get() = withCache { WindowManagerPolicy() }
+
+        /** From [android.content.pm.ActivityInfo] */
+        private const val SCREEN_ORIENTATION_LANDSCAPE = 0
+        private const val SCREEN_ORIENTATION_PORTRAIT = 1
+        private const val SCREEN_ORIENTATION_NOSENSOR = 5
+        private const val SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6
+        private const val SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7
+        private const val SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8
+        private const val SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9
+        private const val SCREEN_ORIENTATION_USER_LANDSCAPE = 11
+        private const val SCREEN_ORIENTATION_USER_PORTRAIT = 12
+        private const val SCREEN_ORIENTATION_LOCKED = 14
+
+        @JsName("from")
+        fun from(
+            focusedAppToken: String = "",
+            forceStatusBar: Boolean = false,
+            forceStatusBarFromKeyguard: Boolean = false,
+            keyguardDrawComplete: Boolean = false,
+            keyguardOccluded: Boolean = false,
+            keyguardOccludedChanged: Boolean = false,
+            keyguardOccludedPending: Boolean = false,
+            lastSystemUiFlags: Int = 0,
+            orientation: Int = 0,
+            rotation: Rotation = Rotation.ROTATION_0,
+            rotationMode: Int = 0,
+            screenOnFully: Boolean = false,
+            windowManagerDrawComplete: Boolean = false
+        ): WindowManagerPolicy = withCache {
+            WindowManagerPolicy(
+                focusedAppToken,
+                forceStatusBar,
+                forceStatusBarFromKeyguard,
+                keyguardDrawComplete,
+                keyguardOccluded,
+                keyguardOccludedChanged,
+                keyguardOccludedPending,
+                lastSystemUiFlags,
+                orientation,
+                rotation,
+                rotationMode,
+                screenOnFully,
+                windowManagerDrawComplete
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerState.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerState.kt
new file mode 100644
index 0000000..04f29c0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerState.kt
@@ -0,0 +1,518 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.CrossPlatform
+import android.tools.common.ITraceEntry
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.IComponentMatcher
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a single WindowManager trace entry.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ *
+ * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
+ */
+@JsExport
+class WindowManagerState(
+    @JsName("elapsedTimestamp") val elapsedTimestamp: Long,
+    @JsName("clockTimestamp") val clockTimestamp: Long?,
+    @JsName("where") val where: String,
+    @JsName("policy") val policy: WindowManagerPolicy?,
+    @JsName("focusedApp") val focusedApp: String,
+    @JsName("focusedDisplayId") val focusedDisplayId: Int,
+    @JsName("_focusedWindow") private val _focusedWindow: String,
+    @JsName("inputMethodWindowAppToken") val inputMethodWindowAppToken: String,
+    @JsName("isHomeRecentsComponent") val isHomeRecentsComponent: Boolean,
+    @JsName("isDisplayFrozen") val isDisplayFrozen: Boolean,
+    @JsName("_pendingActivities") private val _pendingActivities: Array<String>,
+    @JsName("root") val root: RootWindowContainer,
+    @JsName("keyguardControllerState") val keyguardControllerState: KeyguardControllerState
+) : ITraceEntry {
+    override val timestamp =
+        CrossPlatform.timestamp.from(elapsedNanos = elapsedTimestamp, unixNanos = clockTimestamp)
+    @JsName("isVisible") val isVisible: Boolean = true
+    @JsName("stableId")
+    val stableId: String
+        get() = this::class.simpleName ?: error("Unable to determine class")
+    @JsName("isTablet")
+    val isTablet: Boolean
+        get() = displays.any { it.isTablet }
+
+    @JsName("windowContainers")
+    val windowContainers: Array<WindowContainer>
+        get() = root.collectDescendants()
+
+    @JsName("children")
+    val children: Array<WindowContainer>
+        get() = root.children.reversedArray()
+
+    /** Displays in z-order with the top most at the front of the list, starting with primary. */
+    @JsName("displays")
+    val displays: Array<DisplayContent>
+        get() = windowContainers.filterIsInstance<DisplayContent>().toTypedArray()
+
+    /**
+     * Root tasks in z-order with the top most at the front of the list, starting with primary
+     * display.
+     */
+    @JsName("rootTasks")
+    val rootTasks: Array<Task>
+        get() = displays.flatMap { it.rootTasks.toList() }.toTypedArray()
+
+    /** TaskFragments in z-order with the top most at the front of the list. */
+    @JsName("taskFragments")
+    val taskFragments: Array<TaskFragment>
+        get() = windowContainers.filterIsInstance<TaskFragment>().toTypedArray()
+
+    /** Windows in z-order with the top most at the front of the list. */
+    @JsName("windowStates")
+    val windowStates: Array<WindowState>
+        get() = windowContainers.filterIsInstance<WindowState>().toTypedArray()
+
+    @Deprecated("Please use windowStates instead", replaceWith = ReplaceWith("windowStates"))
+    @JsName("windows")
+    val windows: Array<WindowState>
+        get() = windowStates
+
+    @JsName("appWindows")
+    val appWindows: Array<WindowState>
+        get() = windowStates.filter { it.isAppWindow }.toTypedArray()
+    @JsName("nonAppWindows")
+    val nonAppWindows: Array<WindowState>
+        get() = windowStates.filterNot { it.isAppWindow }.toTypedArray()
+    @JsName("aboveAppWindows")
+    val aboveAppWindows: Array<WindowState>
+        get() = windowStates.takeWhile { !appWindows.contains(it) }.toTypedArray()
+    @JsName("belowAppWindows")
+    val belowAppWindows: Array<WindowState>
+        get() =
+            windowStates.dropWhile { !appWindows.contains(it) }.drop(appWindows.size).toTypedArray()
+    @JsName("visibleWindows")
+    val visibleWindows: Array<WindowState>
+        get() =
+            windowStates
+                .filter {
+                    val activities = getActivitiesForWindowState(it)
+                    val windowIsVisible = it.isVisible
+                    val activityIsVisible = activities.any { activity -> activity.isVisible }
+
+                    // for invisible checks it suffices if activity or window is invisible
+                    windowIsVisible && (activityIsVisible || activities.isEmpty())
+                }
+                .toTypedArray()
+    @JsName("visibleAppWindows")
+    val visibleAppWindows: Array<WindowState>
+        get() = visibleWindows.filter { it.isAppWindow }.toTypedArray()
+    @JsName("topVisibleAppWindow")
+    val topVisibleAppWindow: WindowState?
+        get() = visibleAppWindows.firstOrNull()
+    @JsName("pinnedWindows")
+    val pinnedWindows: Array<WindowState>
+        get() = visibleWindows.filter { it.windowingMode == WINDOWING_MODE_PINNED }.toTypedArray()
+    @JsName("pendingActivities")
+    val pendingActivities: Array<Activity>
+        get() = _pendingActivities.mapNotNull { getActivityByName(it) }.toTypedArray()
+    @JsName("focusedWindow")
+    val focusedWindow: WindowState?
+        get() = visibleWindows.firstOrNull { it.name == _focusedWindow }
+
+    val isKeyguardShowing: Boolean
+        get() = keyguardControllerState.isKeyguardShowing
+    val isAodShowing: Boolean
+        get() = keyguardControllerState.isAodShowing
+    /**
+     * Checks if the device state supports rotation, i.e., if the rotation sensor is enabled (e.g.,
+     * launcher) and if the rotation not fixed
+     */
+    @JsName("canRotate")
+    val canRotate: Boolean
+        get() = policy?.isFixedOrientation != true && policy?.isOrientationNoSensor != true
+    @JsName("focusedDisplay")
+    val focusedDisplay: DisplayContent?
+        get() = getDisplay(focusedDisplayId)
+    @JsName("focusedStackId")
+    val focusedStackId: Int
+        get() = focusedDisplay?.focusedRootTaskId ?: -1
+    @JsName("focusedActivity")
+    val focusedActivity: Activity?
+        get() {
+            val focusedDisplay = focusedDisplay
+            val focusedWindow = focusedWindow
+            return when {
+                focusedDisplay != null && focusedDisplay.resumedActivity.isNotEmpty() ->
+                    getActivityByName(focusedDisplay.resumedActivity)
+                focusedWindow != null ->
+                    getActivitiesForWindowState(focusedWindow, focusedDisplayId).firstOrNull()
+                else -> null
+            }
+        }
+    @JsName("resumedActivities")
+    val resumedActivities: Array<Activity>
+        get() =
+            rootTasks
+                .flatMap { it.resumedActivities.toList() }
+                .mapNotNull { getActivityByName(it) }
+                .toTypedArray()
+    @JsName("resumedActivitiesCount")
+    val resumedActivitiesCount: Int
+        get() = resumedActivities.size
+    @JsName("stackCount")
+    val stackCount: Int
+        get() = rootTasks.size
+    @JsName("homeTask")
+    val homeTask: Task?
+        get() = getStackByActivityType(ACTIVITY_TYPE_HOME)?.topTask
+    @JsName("recentsTask")
+    val recentsTask: Task?
+        get() = getStackByActivityType(ACTIVITY_TYPE_RECENTS)?.topTask
+    @JsName("homeActivity")
+    val homeActivity: Activity?
+        get() = homeTask?.activities?.lastOrNull()
+    @JsName("isHomeActivityVisible")
+    val isHomeActivityVisible: Boolean
+        get() {
+            val activity = homeActivity
+            return activity != null && activity.isVisible
+        }
+    @JsName("recentsActivity")
+    val recentsActivity: Activity?
+        get() = recentsTask?.activities?.lastOrNull()
+    @JsName("isRecentsActivityVisible")
+    val isRecentsActivityVisible: Boolean
+        get() {
+            val activity = recentsActivity
+            return activity != null && activity.isVisible
+        }
+    @JsName("frontWindow")
+    val frontWindow: WindowState?
+        get() = windowStates.firstOrNull()
+    @JsName("inputMethodWindowState")
+    val inputMethodWindowState: WindowState?
+        get() = getWindowStateForAppToken(inputMethodWindowAppToken)
+
+    @JsName("getDefaultDisplay")
+    fun getDefaultDisplay(): DisplayContent? = displays.firstOrNull { it.id == DEFAULT_DISPLAY }
+
+    @JsName("getDisplay")
+    fun getDisplay(displayId: Int): DisplayContent? = displays.firstOrNull { it.id == displayId }
+
+    @JsName("countStacks")
+    fun countStacks(windowingMode: Int, activityType: Int): Int {
+        var count = 0
+        for (stack in rootTasks) {
+            if (activityType != ACTIVITY_TYPE_UNDEFINED && activityType != stack.activityType) {
+                continue
+            }
+            if (windowingMode != WINDOWING_MODE_UNDEFINED && windowingMode != stack.windowingMode) {
+                continue
+            }
+            ++count
+        }
+        return count
+    }
+
+    @JsName("getRootTask")
+    fun getRootTask(taskId: Int): Task? = rootTasks.firstOrNull { it.rootTaskId == taskId }
+
+    @JsName("getRotation")
+    fun getRotation(displayId: Int): Rotation =
+        getDisplay(displayId)?.rotation ?: error("Default display not found")
+
+    @JsName("getOrientation")
+    fun getOrientation(displayId: Int): Int =
+        getDisplay(displayId)?.lastOrientation ?: error("Default display not found")
+
+    @JsName("getStackByActivityType")
+    fun getStackByActivityType(activityType: Int): Task? =
+        rootTasks.firstOrNull { it.activityType == activityType }
+
+    @JsName("getStandardStackByWindowingMode")
+    fun getStandardStackByWindowingMode(windowingMode: Int): Task? =
+        rootTasks.firstOrNull {
+            it.activityType == ACTIVITY_TYPE_STANDARD && it.windowingMode == windowingMode
+        }
+
+    @JsName("getActivitiesForWindowState")
+    fun getActivitiesForWindowState(
+        windowState: WindowState,
+        displayId: Int = DEFAULT_DISPLAY
+    ): Array<Activity> {
+        return displays
+            .firstOrNull { it.id == displayId }
+            ?.rootTasks
+            ?.mapNotNull { stack ->
+                stack.getActivity { activity -> activity.hasWindowState(windowState) }
+            }
+            ?.toTypedArray()
+            ?: emptyArray()
+    }
+
+    /**
+     * Get the all activities on display with id [displayId], containing a matching
+     * [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     * @param displayId display where to search the activity
+     */
+    @JsName("getActivitiesForWindow")
+    fun getActivitiesForWindow(
+        componentMatcher: IComponentMatcher,
+        displayId: Int = DEFAULT_DISPLAY
+    ): Array<Activity> {
+        return displays
+            .firstOrNull { it.id == displayId }
+            ?.rootTasks
+            ?.mapNotNull { stack ->
+                stack.getActivity { activity -> activity.hasWindow(componentMatcher) }
+            }
+            ?.toTypedArray()
+            ?: emptyArray()
+    }
+
+    /**
+     * @return if any activity matches [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("containsActivity")
+    fun containsActivity(componentMatcher: IComponentMatcher): Boolean =
+        rootTasks.any { it.containsActivity(componentMatcher) }
+
+    /**
+     * @return the first [Activity] matching [componentMatcher], or null otherwise
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("getActivity")
+    fun getActivity(componentMatcher: IComponentMatcher): Activity? =
+        rootTasks.firstNotNullOfOrNull { it.getActivity(componentMatcher) }
+
+    @JsName("getActivityByName")
+    private fun getActivityByName(activityName: String): Activity? =
+        rootTasks.firstNotNullOfOrNull { task ->
+            task.getActivity { activity -> activity.title.contains(activityName) }
+        }
+
+    /**
+     * @return if any activity matching [componentMatcher] is visible
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("isActivityVisible")
+    fun isActivityVisible(componentMatcher: IComponentMatcher): Boolean =
+        getActivity(componentMatcher)?.isVisible ?: false
+
+    /**
+     * @return if any activity matching [componentMatcher] has state of [activityState]
+     *
+     * @param componentMatcher Components to search
+     * @param activityState expected activity state
+     */
+    @JsName("hasActivityState")
+    fun hasActivityState(componentMatcher: IComponentMatcher, activityState: String): Boolean =
+        rootTasks.any { it.getActivity(componentMatcher)?.state == activityState }
+
+    /**
+     * @return if any pending activities match [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("pendingActivityContain")
+    fun pendingActivityContain(componentMatcher: IComponentMatcher): Boolean =
+        componentMatcher.activityMatchesAnyOf(pendingActivities)
+
+    /**
+     * @return the visible [WindowState]s matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("getMatchingVisibleWindowState")
+    fun getMatchingVisibleWindowState(componentMatcher: IComponentMatcher): Array<WindowState> {
+        return windowStates
+            .filter { it.isSurfaceShown && componentMatcher.windowMatchesAnyOf(it) }
+            .toTypedArray()
+    }
+
+    /** @return the [WindowState] for the nav bar in the display with id [displayId] */
+    @JsName("getNavBarWindow")
+    fun getNavBarWindow(displayId: Int): WindowState? {
+        val navWindow = windowStates.filter { it.isValidNavBarType && it.displayId == displayId }
+
+        // We may need some time to wait for nav bar showing.
+        // It's Ok to get 0 nav bar here.
+        if (navWindow.size > 1) {
+            throw IllegalStateException("There should be at most one navigation bar on a display")
+        }
+        return navWindow.firstOrNull()
+    }
+
+    @JsName("getWindowStateForAppToken")
+    private fun getWindowStateForAppToken(appToken: String): WindowState? =
+        windowStates.firstOrNull { it.token == appToken }
+
+    /**
+     * Checks if there exists a [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("containsWindow")
+    fun containsWindow(componentMatcher: IComponentMatcher): Boolean =
+        componentMatcher.windowMatchesAnyOf(windowStates.asList())
+
+    /**
+     * Check if at least one [WindowState] matching [componentMatcher] is visible
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("isWindowSurfaceShown")
+    fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Boolean =
+        getMatchingVisibleWindowState(componentMatcher).isNotEmpty()
+
+    /** Checks if the state has any window in PIP mode */
+    @JsName("hasPipWindow") fun hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
+
+    /**
+     * Checks that a [WindowState] matching [componentMatcher] is in PIP mode
+     *
+     * @param componentMatcher Components to search
+     */
+    @JsName("isInPipMode")
+    fun isInPipMode(componentMatcher: IComponentMatcher): Boolean =
+        componentMatcher.windowMatchesAnyOf(pinnedWindows.asList())
+
+    @JsName("getZOrder")
+    fun getZOrder(w: WindowState): Int = windowStates.size - windowStates.indexOf(w)
+
+    @JsName("defaultMinimalTaskSize")
+    fun defaultMinimalTaskSize(displayId: Int): Int =
+        dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP.toFloat(), getDisplay(displayId)!!.dpi)
+
+    @JsName("defaultMinimalDisplaySizeForSplitScreen")
+    fun defaultMinimalDisplaySizeForSplitScreen(displayId: Int): Int {
+        return dpToPx(
+            DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP.toFloat(),
+            getDisplay(displayId)!!.dpi
+        )
+    }
+
+    @JsName("getIsIncompleteReason")
+    fun getIsIncompleteReason(): String {
+        return buildString {
+            if (rootTasks.isEmpty()) {
+                append("No stacks found...")
+            }
+            if (focusedStackId == -1) {
+                append("No focused stack found...")
+            }
+            if (focusedActivity == null) {
+                append("No focused activity found...")
+            }
+            if (resumedActivities.isEmpty()) {
+                append("No resumed activities found...")
+            }
+            if (windowStates.isEmpty()) {
+                append("No Windows found...")
+            }
+            if (focusedWindow == null) {
+                append("No Focused Window...")
+            }
+            if (focusedApp.isEmpty()) {
+                append("No Focused App...")
+            }
+            if (keyguardControllerState.isKeyguardShowing) {
+                append("Keyguard showing...")
+            }
+        }
+    }
+
+    @JsName("isComplete") fun isComplete(): Boolean = !isIncomplete()
+    @JsName("isIncomplete")
+    fun isIncomplete(): Boolean {
+        return rootTasks.isEmpty() ||
+            focusedStackId == -1 ||
+            windowStates.isEmpty() ||
+            // overview screen has no focused window
+            ((focusedApp.isEmpty() || focusedWindow == null) && homeActivity == null) ||
+            (focusedActivity == null || resumedActivities.isEmpty()) &&
+                !keyguardControllerState.isKeyguardShowing
+    }
+
+    @JsName("asTrace") fun asTrace(): WindowManagerTrace = WindowManagerTrace(arrayOf(this))
+
+    override fun toString(): String {
+        return "${timestamp}ns"
+    }
+
+    companion object {
+        @JsName("STATE_INITIALIZING") const val STATE_INITIALIZING = "INITIALIZING"
+        @JsName("STATE_RESUMED") const val STATE_RESUMED = "RESUMED"
+        @JsName("STATE_PAUSED") const val STATE_PAUSED = "PAUSED"
+        @JsName("STATE_STOPPED") const val STATE_STOPPED = "STOPPED"
+        @JsName("STATE_DESTROYED") const val STATE_DESTROYED = "DESTROYED"
+        @JsName("APP_STATE_IDLE") const val APP_STATE_IDLE = "APP_STATE_IDLE"
+        @JsName("ACTIVITY_TYPE_UNDEFINED") internal const val ACTIVITY_TYPE_UNDEFINED = 0
+        @JsName("ACTIVITY_TYPE_STANDARD") internal const val ACTIVITY_TYPE_STANDARD = 1
+        @JsName("DEFAULT_DISPLAY") internal const val DEFAULT_DISPLAY = 0
+        @JsName("DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP")
+        internal const val DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440
+        @JsName("ACTIVITY_TYPE_HOME") internal const val ACTIVITY_TYPE_HOME = 2
+        @JsName("ACTIVITY_TYPE_RECENTS") internal const val ACTIVITY_TYPE_RECENTS = 3
+        @JsName("WINDOWING_MODE_UNDEFINED") internal const val WINDOWING_MODE_UNDEFINED = 0
+        @JsName("DENSITY_DEFAULT") private const val DENSITY_DEFAULT = 160
+        /** @see android.app.WindowConfiguration.WINDOWING_MODE_PINNED */
+        @JsName("WINDOWING_MODE_PINNED") private const val WINDOWING_MODE_PINNED = 2
+
+        /** @see android.view.WindowManager.LayoutParams */
+        @JsName("TYPE_NAVIGATION_BAR_PANEL") internal const val TYPE_NAVIGATION_BAR_PANEL = 2024
+
+        // Default minimal size of resizable task, used if none is set explicitly.
+        // Must be kept in sync with 'default_minimal_size_resizable_task'
+        // dimen from frameworks/base.
+        @JsName("DEFAULT_RESIZABLE_TASK_SIZE_DP")
+        internal const val DEFAULT_RESIZABLE_TASK_SIZE_DP = 220
+
+        @JsName("dpToPx")
+        fun dpToPx(dp: Float, densityDpi: Int): Int {
+            return (dp * densityDpi / DENSITY_DEFAULT + 0.5f).toInt()
+        }
+    }
+    override fun equals(other: Any?): Boolean {
+        return other is WindowManagerState && other.timestamp == this.timestamp
+    }
+
+    override fun hashCode(): Int {
+        var result = where.hashCode()
+        result = 31 * result + (policy?.hashCode() ?: 0)
+        result = 31 * result + focusedApp.hashCode()
+        result = 31 * result + focusedDisplayId
+        result = 31 * result + focusedWindow.hashCode()
+        result = 31 * result + inputMethodWindowAppToken.hashCode()
+        result = 31 * result + isHomeRecentsComponent.hashCode()
+        result = 31 * result + isDisplayFrozen.hashCode()
+        result = 31 * result + pendingActivities.contentHashCode()
+        result = 31 * result + root.hashCode()
+        result = 31 * result + keyguardControllerState.hashCode()
+        result = 31 * result + timestamp.hashCode()
+        result = 31 * result + isVisible.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTrace.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTrace.kt
new file mode 100644
index 0000000..c213a78
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTrace.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.ITrace
+import android.tools.common.Rotation
+import android.tools.common.Timestamp
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Contains a collection of parsed WindowManager trace entries and assertions to apply over a single
+ * entry.
+ *
+ * Each entry is parsed into a list of [WindowManagerState] objects.
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+data class WindowManagerTrace(override val entries: Array<WindowManagerState>) :
+    ITrace<WindowManagerState> {
+
+    @JsName("isTablet")
+    val isTablet: Boolean
+        get() = entries.any { it.isTablet }
+
+    override fun toString(): String {
+        return "WindowManagerTrace(Start: ${entries.firstOrNull()}, " +
+            "End: ${entries.lastOrNull()})"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowManagerTrace) return false
+
+        if (!entries.contentEquals(other.entries)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return entries.contentHashCode()
+    }
+
+    /** Get the initial rotation */
+    fun getInitialRotation(): Rotation {
+        if (entries.isEmpty()) {
+            throw RuntimeException("WindowManager Trace has no entries")
+        }
+        val firstWmState = entries[0]
+        return firstWmState.policy?.rotation
+            ?: run { throw RuntimeException("Wm state has no policy") }
+    }
+
+    /** Get the final rotation */
+    fun getFinalRotation(): Rotation {
+        if (entries.isEmpty()) {
+            throw RuntimeException("WindowManager Trace has no entries")
+        }
+        val lastWmState = entries.last()
+        return lastWmState.policy?.rotation
+            ?: run { throw RuntimeException("Wm state has no policy") }
+    }
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): WindowManagerTrace {
+        return WindowManagerTrace(
+            entries
+                .dropWhile { it.timestamp < startTimestamp }
+                .dropLastWhile { it.timestamp > endTimestamp }
+                .toTypedArray()
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilder.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilder.kt
new file mode 100644
index 0000000..cd87bda
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilder.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+class WindowManagerTraceEntryBuilder(
+    _elapsedTimestamp: String,
+    @JsName("policy") private val policy: WindowManagerPolicy?,
+    @JsName("focusedApp") private val focusedApp: String,
+    @JsName("focusedDisplayId") private val focusedDisplayId: Int,
+    @JsName("focusedWindow") private val focusedWindow: String,
+    @JsName("inputMethodWindowAppToken") private val inputMethodWindowAppToken: String,
+    @JsName("isHomeRecentsComponent") private val isHomeRecentsComponent: Boolean,
+    @JsName("isDisplayFrozen") private val isDisplayFrozen: Boolean,
+    @JsName("pendingActivities") private val pendingActivities: Array<String>,
+    @JsName("root") private val root: RootWindowContainer,
+    @JsName("keyguardControllerState") private val keyguardControllerState: KeyguardControllerState,
+    @JsName("where") private val where: String = "",
+    realToElapsedTimeOffsetNs: String? = null,
+) {
+    // Necessary for compatibility with JS number type
+    @JsName("elapsedTimestamp") private val elapsedTimestamp: Long = _elapsedTimestamp.toLong()
+    @JsName("realTimestamp")
+    private val realTimestamp: Long? =
+        if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs.toLong() != 0L) {
+            realToElapsedTimeOffsetNs.toLong() + _elapsedTimestamp.toLong()
+        } else {
+            null
+        }
+
+    /** Constructs the window manager trace entry. */
+    @JsName("build")
+    fun build(): WindowManagerState {
+        return WindowManagerState(
+            elapsedTimestamp,
+            realTimestamp,
+            where,
+            policy,
+            focusedApp,
+            focusedDisplayId,
+            focusedWindow,
+            inputMethodWindowAppToken,
+            isHomeRecentsComponent,
+            isDisplayFrozen,
+            pendingActivities,
+            root,
+            keyguardControllerState
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowState.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowState.kt
new file mode 100644
index 0000000..28372f4
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowState.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.traces.wm
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.Size
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+/**
+ * Represents a window in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class WindowState(
+    @JsName("attributes") val attributes: WindowLayoutParams,
+    @JsName("displayId") val displayId: Int,
+    @JsName("stackId") val stackId: Int,
+    @JsName("layer") val layer: Int,
+    @JsName("isSurfaceShown") val isSurfaceShown: Boolean,
+    @JsName("windowType") val windowType: Int,
+    @JsName("requestedSize") val requestedSize: Size,
+    @JsName("surfacePosition") val surfacePosition: Rect?,
+    @JsName("frame") val frame: Rect,
+    @JsName("containingFrame") val containingFrame: Rect,
+    @JsName("parentFrame") val parentFrame: Rect,
+    @JsName("contentFrame") val contentFrame: Rect,
+    @JsName("contentInsets") val contentInsets: Rect,
+    @JsName("surfaceInsets") val surfaceInsets: Rect,
+    @JsName("givenContentInsets") val givenContentInsets: Rect,
+    @JsName("crop") val crop: Rect,
+    windowContainer: WindowContainer,
+    @JsName("isAppWindow") val isAppWindow: Boolean
+) : WindowContainer(windowContainer, getWindowTitle(windowContainer.title)) {
+    override val isVisible: Boolean = windowContainer.isVisible && attributes.alpha > 0
+
+    override val isFullscreen: Boolean
+        get() = this.attributes.flags.and(FLAG_FULLSCREEN) > 0
+    @JsName("isStartingWindow") val isStartingWindow: Boolean = windowType == WINDOW_TYPE_STARTING
+    @JsName("isExitingWindow") val isExitingWindow: Boolean = windowType == WINDOW_TYPE_EXITING
+    @JsName("isDebuggerWindow") val isDebuggerWindow: Boolean = windowType == WINDOW_TYPE_DEBUGGER
+    @JsName("isValidNavBarType") val isValidNavBarType: Boolean = attributes.isValidNavBarType
+
+    @JsName("frameRegion") val frameRegion: Region = Region.from(frame)
+
+    @JsName("getWindowTypeSuffix")
+    private fun getWindowTypeSuffix(windowType: Int): String =
+        when (windowType) {
+            WINDOW_TYPE_STARTING -> " STARTING"
+            WINDOW_TYPE_EXITING -> " EXITING"
+            WINDOW_TYPE_DEBUGGER -> " DEBUGGER"
+            else -> ""
+        }
+
+    override fun toString(): String =
+        "${this::class.simpleName}: " +
+            "{$token $title${getWindowTypeSuffix(windowType)}} " +
+            "type=${attributes.type} cf=$containingFrame pf=$parentFrame"
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowState) return false
+
+        if (name != other.name) return false
+        if (attributes != other.attributes) return false
+        if (displayId != other.displayId) return false
+        if (stackId != other.stackId) return false
+        if (layer != other.layer) return false
+        if (isSurfaceShown != other.isSurfaceShown) return false
+        if (windowType != other.windowType) return false
+        if (requestedSize != other.requestedSize) return false
+        if (surfacePosition != other.surfacePosition) return false
+        if (frame != other.frame) return false
+        if (containingFrame != other.containingFrame) return false
+        if (parentFrame != other.parentFrame) return false
+        if (contentFrame != other.contentFrame) return false
+        if (contentInsets != other.contentInsets) return false
+        if (surfaceInsets != other.surfaceInsets) return false
+        if (givenContentInsets != other.givenContentInsets) return false
+        if (crop != other.crop) return false
+        if (isAppWindow != other.isAppWindow) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = attributes.hashCode()
+        result = 31 * result + displayId
+        result = 31 * result + stackId
+        result = 31 * result + layer
+        result = 31 * result + isSurfaceShown.hashCode()
+        result = 31 * result + windowType
+        result = 31 * result + frame.hashCode()
+        result = 31 * result + containingFrame.hashCode()
+        result = 31 * result + parentFrame.hashCode()
+        result = 31 * result + contentFrame.hashCode()
+        result = 31 * result + contentInsets.hashCode()
+        result = 31 * result + surfaceInsets.hashCode()
+        result = 31 * result + givenContentInsets.hashCode()
+        result = 31 * result + crop.hashCode()
+        result = 31 * result + isAppWindow.hashCode()
+        result = 31 * result + isStartingWindow.hashCode()
+        result = 31 * result + isExitingWindow.hashCode()
+        result = 31 * result + isDebuggerWindow.hashCode()
+        result = 31 * result + isValidNavBarType.hashCode()
+        result = 31 * result + frameRegion.hashCode()
+        return result
+    }
+
+    companion object {
+        /**
+         * From {@see android.view.WindowManager.FLAG_FULLSCREEN}.
+         *
+         * This class is shared between JVM and JS (Winscope) and cannot access Android internals
+         */
+        @JsName("FLAG_FULLSCREEN") private const val FLAG_FULLSCREEN = 0x00000400
+        @JsName("WINDOW_TYPE_STARTING") internal const val WINDOW_TYPE_STARTING = 1
+        @JsName("WINDOW_TYPE_EXITING") internal const val WINDOW_TYPE_EXITING = 2
+        @JsName("WINDOW_TYPE_DEBUGGER") private const val WINDOW_TYPE_DEBUGGER = 3
+
+        @JsName("STARTING_WINDOW_PREFIX") internal const val STARTING_WINDOW_PREFIX = "Starting "
+        @JsName("DEBUGGER_WINDOW_PREFIX")
+        internal const val DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: "
+
+        @JsName("getWindowTitle")
+        private fun getWindowTitle(title: String): String {
+            return when {
+                // Existing code depends on the prefix being removed
+                title.startsWith(STARTING_WINDOW_PREFIX) ->
+                    title.substring(STARTING_WINDOW_PREFIX.length)
+                title.startsWith(DEBUGGER_WINDOW_PREFIX) ->
+                    title.substring(DEBUGGER_WINDOW_PREFIX.length)
+                else -> title
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowToken.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowToken.kt
new file mode 100644
index 0000000..790bd62
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowToken.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+
+/**
+ * Represents a window token in the window manager hierarchy
+ *
+ * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
+ * Java/Android functionality
+ */
+@JsExport
+class WindowToken(windowContainer: WindowContainer) : WindowContainer(windowContainer) {
+    override val isVisible: Boolean
+        get() = false
+    override fun toString(): String {
+        return "${this::class.simpleName}: {$token $title}"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WindowToken) return false
+        if (!super.equals(other)) return false
+
+        if (isVisible != other.isVisible) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + isVisible.hashCode()
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/common/traces/wm/WindowingMode.kt b/libraries/flicker/src/android/tools/common/traces/wm/WindowingMode.kt
new file mode 100644
index 0000000..5e007f7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/common/traces/wm/WindowingMode.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.traces.wm
+
+import kotlin.js.JsExport
+import kotlin.js.JsName
+
+@JsExport
+enum class WindowingMode(val value: Int) {
+    /** Windowing mode is currently not defined. */
+    WINDOWING_MODE_UNDEFINED(0),
+
+    /** Occupies the full area of the screen or the parent container. */
+    WINDOWING_MODE_FULLSCREEN(1),
+
+    /** Always on-top (always visible). of other siblings in its parent container. */
+    WINDOWING_MODE_PINNED(2),
+
+    /** Can be freely resized within its parent container. */
+    WINDOWING_MODE_FREEFORM(5),
+
+    /** Generic multi-window with no presentation attribution from the window manager. */
+    WINDOWING_MODE_MULTI_WINDOW(6);
+
+    companion object {
+        @JsName("fromInt")
+        fun fromInt(value: Int) =
+            values().firstOrNull { it.value == value }
+                ?: error("No valid windowing mode for id $value")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/BrowserAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/BrowserAppHelper.kt
new file mode 100644
index 0000000..56bd651
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/BrowserAppHelper.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/**
+ * Helper to launch the default browser app (compatible with AOSP)
+ *
+ * This helper has no other functionality but the app launch.
+ *
+ * This helper is used to launch an app after some operations (e.g., navigation mode change), so
+ * that the device is stable before executing flicker tests
+ */
+class BrowserAppHelper(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        BrowserAppHelper.Companion.getBrowserName(pkgManager),
+        BrowserAppHelper.Companion.getBrowserComponent(pkgManager)
+    ) {
+    override fun getOpenAppIntent(): Intent =
+        pkgManager.getLaunchIntentForPackage(packageName)
+            ?: error("Unable to find intent for browser")
+
+    companion object {
+        private fun getBrowserIntent(): Intent {
+            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            return intent
+        }
+
+        private fun getBrowserName(pkgManager: PackageManager): String {
+            val intent = BrowserAppHelper.Companion.getBrowserIntent()
+            val resolveInfo =
+                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                    ?: error("Unable to resolve browser activity")
+
+            return resolveInfo.loadLabel(pkgManager).toString()
+        }
+
+        private fun getBrowserComponent(pkgManager: PackageManager): ComponentNameMatcher {
+            val intent = BrowserAppHelper.Companion.getBrowserIntent()
+            val resolveInfo =
+                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                    ?: error("Unable to resolve browser activity")
+            return ComponentNameMatcher(resolveInfo.activityInfo.packageName, className = "")
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/CalculatorAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/CalculatorAppHelper.kt
new file mode 100644
index 0000000..bee7727
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/CalculatorAppHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/**
+ * Helper to launch the calculator app (not compatible with AOSP)
+ *
+ * This helper has no other functionality but the app launch.
+ */
+class CalculatorAppHelper(instrumentation: Instrumentation) :
+    StandardAppHelper(
+        instrumentation,
+        "Calculator",
+        ComponentNameMatcher(
+            packageName = "com.google.android.calculator",
+            className = "com.android.calculator2.Calculator"
+        )
+    )
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/CalendarAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/CalendarAppHelper.kt
new file mode 100644
index 0000000..566669c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/CalendarAppHelper.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/** Helper to launch the Calendar app. */
+class CalendarAppHelper
+@JvmOverloads
+constructor(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        CalendarAppHelper.Companion.getCalendarLauncherName(pkgManager),
+        CalendarAppHelper.Companion.getCalendarComponent(pkgManager)
+    ) {
+    companion object {
+        private fun getCalendarIntent(): Intent {
+            val epochEventStartTime = 0
+            val uri = Uri.parse("content://com.android.calendar/time/" + epochEventStartTime)
+            val intent = Intent(Intent.ACTION_VIEW)
+            intent.data = uri
+            intent.putExtra("VIEW", "DAY")
+            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+            return intent
+        }
+
+        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
+            val intent = CalendarAppHelper.Companion.getCalendarIntent()
+            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                ?: error("unable to resolve calendar activity")
+        }
+
+        private fun getCalendarComponent(pkgManager: PackageManager): ComponentNameMatcher {
+            val resolveInfo = CalendarAppHelper.Companion.getResolveInfo(pkgManager)
+            return ComponentNameMatcher(
+                resolveInfo.activityInfo.packageName,
+                className = resolveInfo.activityInfo.name
+            )
+        }
+
+        private fun getCalendarLauncherName(pkgManager: PackageManager): String {
+            val resolveInfo = CalendarAppHelper.Companion.getResolveInfo(pkgManager)
+            return resolveInfo.loadLabel(pkgManager).toString()
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/CameraAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/CameraAppHelper.kt
new file mode 100644
index 0000000..c033cc8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/CameraAppHelper.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.provider.MediaStore
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/** Helper to launch the Camera app. */
+class CameraAppHelper
+@JvmOverloads
+constructor(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        CameraAppHelper.Companion.getCameraLauncherName(pkgManager),
+        CameraAppHelper.Companion.getCameraComponent(pkgManager)
+    ) {
+
+    override fun getOpenAppIntent(): Intent =
+        pkgManager.getLaunchIntentForPackage(packageName)
+            ?: error("Unable to find intent for camera")
+
+    companion object {
+        private fun getCameraIntent(): Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+
+        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo =
+            pkgManager.resolveActivity(
+                CameraAppHelper.Companion.getCameraIntent(),
+                PackageManager.MATCH_DEFAULT_ONLY
+            )
+                ?: error("unable to resolve camera activity")
+
+        private fun getCameraComponent(pkgManager: PackageManager): ComponentNameMatcher =
+            ComponentNameMatcher(
+                getResolveInfo(pkgManager).activityInfo.packageName,
+                className = ""
+            )
+
+        private fun getCameraLauncherName(pkgManager: PackageManager): String =
+            CameraAppHelper.Companion.getResolveInfo(pkgManager).loadLabel(pkgManager).toString()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/GmailAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/GmailAppHelper.kt
new file mode 100644
index 0000000..4519ceb
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/GmailAppHelper.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/** Helper to launch the Gmail app (not compatible with AOSP) */
+class GmailAppHelper(instrumentation: Instrumentation) :
+    StandardAppHelper(
+        instrumentation,
+        "Gmail",
+        ComponentNameMatcher(
+            packageName = "com.google.android.gm",
+            className = "com.google.android.gm.ConversationListActivityGmail"
+        )
+    )
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/MapsAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/MapsAppHelper.kt
new file mode 100644
index 0000000..5987b42
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/MapsAppHelper.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/** Helper to launch the Maps app (not compatible with AOSP) */
+class MapsAppHelper
+@JvmOverloads
+constructor(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        MapsAppHelper.Companion.getMapLauncherName(pkgManager),
+        MapsAppHelper.Companion.getMapComponent(pkgManager)
+    ) {
+    companion object {
+        private fun getMapIntent(): Intent {
+            val gmmIntentUri = Uri.parse("google.streetview:cbll=46.414382,10.013988")
+            return Intent(Intent.ACTION_VIEW, gmmIntentUri)
+        }
+
+        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
+            val intent = MapsAppHelper.Companion.getMapIntent()
+            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                ?: error("unable to resolve camera activity")
+        }
+
+        private fun getMapComponent(pkgManager: PackageManager): ComponentNameMatcher {
+            val resolveInfo = MapsAppHelper.Companion.getResolveInfo(pkgManager)
+            return ComponentNameMatcher(
+                resolveInfo.activityInfo.packageName,
+                className = resolveInfo.activityInfo.name
+            )
+        }
+
+        private fun getMapLauncherName(pkgManager: PackageManager): String {
+            val resolveInfo = MapsAppHelper.Companion.getResolveInfo(pkgManager)
+            return resolveInfo.loadLabel(pkgManager).toString()
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/MessagingAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/MessagingAppHelper.kt
new file mode 100644
index 0000000..294f3eb
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/MessagingAppHelper.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/**
+ * Helper to launch the default messaging app (compatible with AOSP)
+ *
+ * This helper has no other functionality but the app launch.
+ */
+class MessagingAppHelper(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        "SampleApp",
+        MessagingAppHelper.Companion.getMessagesComponent(pkgManager)
+    ) {
+    override fun getOpenAppIntent(): Intent =
+        pkgManager.getLaunchIntentForPackage(packageName)
+            ?: error("Unable to find intent for browser")
+
+    companion object {
+        private fun getMessagesIntent(): Intent {
+            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:"))
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            return intent
+        }
+
+        private fun getMessagesComponent(pkgManager: PackageManager): ComponentNameMatcher {
+            val intent = MessagingAppHelper.Companion.getMessagesIntent()
+            val resolveInfo =
+                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                    ?: error("Unable to resolve browser activity")
+            return ComponentNameMatcher(resolveInfo.activityInfo.packageName, className = "")
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/StandardAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/StandardAppHelper.kt
new file mode 100644
index 0000000..c5ec8e2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/StandardAppHelper.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.ActivityManager
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.platform.helpers.AbstractStandardAppHelper
+import android.tools.common.CrossPlatform
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.datatypes.component.IComponentNameMatcher
+import android.tools.common.traces.Condition
+import android.tools.common.traces.ConditionsFactory
+import android.tools.common.traces.DeviceStateDump
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+/**
+ * Class to take advantage of {@code IAppHelper} interface so the same test can be run against first
+ * party and third party apps.
+ */
+open class StandardAppHelper(
+    instr: Instrumentation,
+    val appName: String,
+    val componentMatcher: ComponentNameMatcher
+) : AbstractStandardAppHelper(instr), IComponentNameMatcher by componentMatcher {
+    constructor(
+        instr: Instrumentation,
+        appName: String,
+        packageName: String,
+        activity: String
+    ) : this(instr, appName, ComponentNameMatcher(packageName, ".$activity"))
+
+    protected val pkgManager: PackageManager = instr.context.packageManager
+
+    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
+
+    private val activityManager: ActivityManager?
+        get() = mInstrumentation.context.getSystemService(ActivityManager::class.java)
+
+    protected val context: Context
+        get() = mInstrumentation.context
+
+    override val packageName = componentMatcher.packageName
+
+    override val className = componentMatcher.className
+
+    protected val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+
+    private fun getAppSelector(expectedPackageName: String): BySelector {
+        val expected = expectedPackageName.ifEmpty { packageName }
+        return By.pkg(expected).depth(0)
+    }
+
+    override fun open() {
+        open(`package`)
+    }
+
+    protected fun open(expectedPackageName: String) {
+        tapl.goHome().switchToAllApps().getAppIcon(launcherName).launch(expectedPackageName)
+    }
+
+    /** {@inheritDoc} */
+    override fun getPackage(): String {
+        return packageName
+    }
+
+    /** {@inheritDoc} */
+    override fun getOpenAppIntent(): Intent {
+        val intent = Intent()
+        intent.addCategory(Intent.CATEGORY_LAUNCHER)
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        intent.component = ComponentName(packageName, className)
+        return intent
+    }
+
+    /** {@inheritDoc} */
+    override fun getLauncherName(): String {
+        return appName
+    }
+
+    /** {@inheritDoc} */
+    override fun dismissInitialDialogs() {}
+
+    /** {@inheritDoc} */
+    override fun exit() {
+        CrossPlatform.log.withTracing("exit") {
+            // Ensure all testing components end up being closed.
+            activityManager?.forceStopPackage(packageName)
+        }
+    }
+
+    /** Exits the activity and wait for activity destroyed */
+    fun exit(wmHelper: WindowManagerStateHelper) {
+        CrossPlatform.log.withTracing("${this::class.simpleName}#exitAndWait") {
+            exit()
+            waitForActivityDestroyed(wmHelper)
+        }
+    }
+
+    /** Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} */
+    private fun waitForActivityDestroyed(wmHelper: WindowManagerStateHelper) {
+        val waitMsg =
+            "state of ${componentMatcher.toActivityIdentifier()} to be " +
+                WindowManagerState.STATE_DESTROYED
+        wmHelper
+            .StateSyncBuilder()
+            .add(waitMsg) {
+                !it.wmState.containsActivity(componentMatcher) ||
+                    it.wmState.hasActivityState(
+                        componentMatcher,
+                        WindowManagerState.STATE_DESTROYED
+                    )
+            }
+            .withAppTransitionIdle()
+            .waitForAndVerify()
+    }
+
+    private fun launchAppViaIntent(
+        action: String? = null,
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        CrossPlatform.log.withTracing("${this::class.simpleName}#launchAppViaIntent") {
+            val intent = openAppIntent
+            intent.action = action
+            stringExtras.forEach { intent.putExtra(it.key, it.value) }
+            context.startActivity(intent)
+        }
+    }
+
+    /**
+     * Launches the app through an intent instead of interacting with the launcher.
+     *
+     * Uses UiAutomation to detect when the app is open
+     */
+    @JvmOverloads
+    open fun launchViaIntent(
+        expectedPackageName: String = "",
+        action: String? = null,
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        launchAppViaIntent(action, stringExtras)
+        val appSelector = getAppSelector(expectedPackageName)
+        uiDevice.wait(
+            Until.hasObject(appSelector),
+            StandardAppHelper.Companion.APP_LAUNCH_WAIT_TIME_MS
+        )
+    }
+
+    /**
+     * Launches the app through an intent instead of interacting with the launcher and waits until
+     * the app window is visible
+     */
+    @JvmOverloads
+    open fun launchViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
+        action: String? = null,
+        stringExtras: Map<String, String> = mapOf(),
+        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
+    ) =
+        launchViaIntentAndWaitShown(
+            wmHelper,
+            launchedAppComponentMatcherOverride,
+            action,
+            stringExtras,
+            waitConditions
+        )
+
+    /**
+     * Launches the app through an intent instead of interacting with the launcher and waits until
+     * the app window is visible
+     */
+    protected fun launchViaIntentAndWaitShown(
+        wmHelper: WindowManagerStateHelper,
+        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
+        action: String? = null,
+        stringExtras: Map<String, String> = mapOf(),
+        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
+    ) {
+        launchAppViaIntent(action, stringExtras)
+        doWaitShown(wmHelper, launchedAppComponentMatcherOverride, waitConditions)
+    }
+
+    private fun doWaitShown(
+        wmHelper: WindowManagerStateHelper,
+        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
+        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
+    ) {
+        CrossPlatform.log.withTracing("${this::class.simpleName}#doWaitShown") {
+            val expectedWindow = launchedAppComponentMatcherOverride ?: componentMatcher
+            val builder =
+                wmHelper
+                    .StateSyncBuilder()
+                    .add(ConditionsFactory.isWMStateComplete())
+                    .withAppTransitionIdle()
+                    .withWindowSurfaceAppeared(expectedWindow)
+
+            waitConditions.forEach { builder.add(it) }
+            builder.waitForAndVerify()
+
+            // During seamless rotation the app window is shown
+            val currWmState = wmHelper.currentState.wmState
+            if (currWmState.visibleWindows.none { it.isFullscreen }) {
+                wmHelper
+                    .StateSyncBuilder()
+                    .withNavOrTaskBarVisible()
+                    .withStatusBarVisible()
+                    .waitForAndVerify()
+            }
+        }
+    }
+
+    fun isAvailable(): Boolean {
+        return try {
+            pkgManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
+            true
+        } catch (e: PackageManager.NameNotFoundException) {
+            false
+        }
+    }
+
+    companion object {
+        private const val APP_LAUNCH_WAIT_TIME_MS = 10000L
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/apphelpers/YouTubeAppHelper.kt b/libraries/flicker/src/android/tools/device/apphelpers/YouTubeAppHelper.kt
new file mode 100644
index 0000000..87cfee1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/apphelpers/YouTubeAppHelper.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.device.apphelpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+/**
+ * Helper to launch Youtube (not compatible with AOSP)
+ *
+ * This helper has no other functionality but the app launch.
+ */
+class YouTubeAppHelper(
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
+) :
+    StandardAppHelper(
+        instrumentation,
+        getYoutubeLauncherName(pkgManager),
+        getYoutubeComponent(pkgManager)
+    ) {
+    companion object {
+        private fun getYoutubeIntent(pkgManager: PackageManager): Intent {
+            return pkgManager.getLaunchIntentForPackage("com.google.android.youtube")
+                ?: error("Youtube launch intent not found")
+        }
+
+        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
+            val intent = getYoutubeIntent(pkgManager)
+            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                ?: error("unable to resolve calendar activity")
+        }
+
+        private fun getYoutubeComponent(pkgManager: PackageManager): ComponentNameMatcher {
+            val resolveInfo = getResolveInfo(pkgManager)
+            return ComponentNameMatcher(
+                resolveInfo.activityInfo.packageName,
+                className = resolveInfo.activityInfo.name
+            )
+        }
+
+        private fun getYoutubeLauncherName(pkgManager: PackageManager): String {
+            val resolveInfo = getResolveInfo(pkgManager)
+            return resolveInfo.loadLabel(pkgManager).toString()
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/Consts.kt b/libraries/flicker/src/android/tools/device/flicker/Consts.kt
new file mode 100644
index 0000000..181bee3
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/Consts.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.device.flicker
+
+import android.os.SystemProperties
+
+const val IS_FAAS_ENABLED = true
+val isShellTransitionsEnabled = SystemProperties.getBoolean("persist.wm.debug.shell_transit", true)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/EventLogTags.eventlog b/libraries/flicker/src/android/tools/device/flicker/EventLogTags.eventlog
similarity index 100%
rename from libraries/flicker/src/com/android/server/wm/flicker/EventLogTags.eventlog
rename to libraries/flicker/src/android/tools/device/flicker/EventLogTags.eventlog
diff --git a/libraries/flicker/src/android/tools/device/flicker/FlickerService.kt b/libraries/flicker/src/android/tools/device/flicker/FlickerService.kt
new file mode 100644
index 0000000..dca030f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/FlickerService.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.device.flicker
+
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.flicker.IFlickerService
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.flicker.assertors.factories.AssertionFactory
+import android.tools.common.flicker.assertors.factories.CombinedAssertionFactory
+import android.tools.common.flicker.assertors.factories.GeneratedAssertionsFactory
+import android.tools.common.flicker.assertors.factories.IAssertionFactory
+import android.tools.common.flicker.assertors.runners.AssertionRunner
+import android.tools.common.flicker.assertors.runners.IAssertionRunner
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.common.flicker.extractors.CombinedScenarioExtractor
+import android.tools.common.flicker.extractors.IScenarioExtractor
+import android.tools.common.io.IReader
+
+/** Contains the logic for Flicker as a Service. */
+class FlickerService(
+    val scenarioExtractor: IScenarioExtractor =
+        CombinedScenarioExtractor(FlickerServiceConfig.getExtractors()),
+    val assertionFactory: IAssertionFactory =
+        CombinedAssertionFactory(listOf(AssertionFactory(), GeneratedAssertionsFactory())),
+    val assertionRunner: IAssertionRunner = AssertionRunner(),
+) : IFlickerService {
+    /**
+     * The entry point for WM Flicker Service.
+     *
+     * @param reader A flicker trace reader
+     * @return A list of assertion results
+     */
+    override fun process(reader: IReader): List<IAssertionResult> {
+        return CrossPlatform.log.withTracing("FlickerService#process") {
+            try {
+                require(isShellTransitionsEnabled) {
+                    "Shell transitions must be enabled for FaaS to work!"
+                }
+
+                val scenarioInstances = scenarioExtractor.extract(reader)
+                val assertions =
+                    scenarioInstances.flatMap { assertionFactory.generateAssertionsFor(it) }
+                assertionRunner.execute(assertions)
+            } catch (exception: Throwable) {
+                CrossPlatform.log.e("$FLICKER_TAG-ASSERT", "FAILED PROCESSING", exception)
+                throw exception
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/FlickerServiceResultsCollector.kt b/libraries/flicker/src/android/tools/device/flicker/FlickerServiceResultsCollector.kt
new file mode 100644
index 0000000..3cc6bb9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/FlickerServiceResultsCollector.kt
@@ -0,0 +1,234 @@
+/*
+ * 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.device.flicker
+
+import android.app.Instrumentation
+import android.device.collectors.BaseMetricListener
+import android.device.collectors.DataRecord
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.flicker.AssertionInvocationGroup
+import android.tools.common.flicker.IFlickerService
+import android.tools.common.flicker.ITracesCollector
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.device.flicker.legacy.runner.ExecutionError
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.annotations.VisibleForTesting
+import org.junit.runner.Description
+import org.junit.runner.Result
+import org.junit.runner.notification.Failure
+
+/**
+ * Collects all the Flicker Service's metrics which are then uploaded for analysis and monitoring to
+ * the CrystalBall database.
+ */
+class FlickerServiceResultsCollector(
+    private val tracesCollector: ITracesCollector,
+    private val flickerService: IFlickerService = FlickerService(),
+    instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    private val collectMetricsPerTest: Boolean = true,
+    private val reportOnlyForPassingTests: Boolean = true
+) : BaseMetricListener(), IFlickerServiceResultsCollector {
+    private var hasFailedTest = false
+    private var testSkipped = false
+
+    private val _executionErrors = mutableListOf<ExecutionError>()
+    override val executionErrors
+        get() = _executionErrors
+
+    @VisibleForTesting val assertionResults = mutableListOf<IAssertionResult>()
+    @VisibleForTesting
+    val assertionResultsByTest = mutableMapOf<Description, List<IAssertionResult>>()
+
+    init {
+        setInstrumentation(instrumentation)
+    }
+
+    override fun onTestRunStart(runData: DataRecord, description: Description) {
+        errorReportingBlock {
+            CrossPlatform.log.i(
+                LOG_TAG,
+                "onTestRunStart :: collectMetricsPerTest = $collectMetricsPerTest"
+            )
+            if (!collectMetricsPerTest) {
+                hasFailedTest = false
+                tracesCollector.start()
+            }
+        }
+    }
+
+    override fun onTestStart(testData: DataRecord, description: Description) {
+        errorReportingBlock {
+            CrossPlatform.log.i(
+                LOG_TAG,
+                "onTestStart :: collectMetricsPerTest = $collectMetricsPerTest"
+            )
+            if (collectMetricsPerTest) {
+                hasFailedTest = false
+                tracesCollector.start()
+            }
+            testSkipped = false
+        }
+    }
+
+    override fun onTestFail(testData: DataRecord, description: Description, failure: Failure) {
+        errorReportingBlock {
+            CrossPlatform.log.i(LOG_TAG, "onTestFail")
+            hasFailedTest = true
+        }
+    }
+
+    override fun testSkipped(description: Description) {
+        errorReportingBlock {
+            CrossPlatform.log.i(LOG_TAG, "testSkipped")
+            testSkipped = true
+        }
+    }
+
+    override fun onTestEnd(testData: DataRecord, description: Description) {
+        errorReportingBlock {
+            CrossPlatform.log.i(
+                LOG_TAG,
+                "onTestEnd :: collectMetricsPerTest = $collectMetricsPerTest"
+            )
+            if (collectMetricsPerTest && !testSkipped) {
+                stopTracingAndCollectFlickerMetrics(testData, description)
+            }
+        }
+    }
+
+    override fun onTestRunEnd(runData: DataRecord, result: Result) {
+        errorReportingBlock {
+            CrossPlatform.log.i(
+                LOG_TAG,
+                "onTestRunEnd :: collectMetricsPerTest = $collectMetricsPerTest"
+            )
+            if (!collectMetricsPerTest) {
+                stopTracingAndCollectFlickerMetrics(runData)
+            }
+        }
+    }
+
+    private fun stopTracingAndCollectFlickerMetrics(
+        dataRecord: DataRecord,
+        description: Description? = null
+    ) {
+        CrossPlatform.log.i(LOG_TAG, "Stopping trace collection")
+        tracesCollector.stop()
+        CrossPlatform.log.i(LOG_TAG, "Stopped trace collection")
+        if (reportOnlyForPassingTests && hasFailedTest) {
+            return
+        }
+
+        val reader = tracesCollector.getResultReader()
+        dataRecord.addStringMetric(WINSCOPE_FILE_PATH_KEY, reader.artifactPath)
+        CrossPlatform.log.i(LOG_TAG, "Processing traces")
+        val results = flickerService.process(reader)
+        CrossPlatform.log.i(LOG_TAG, "Got ${results.size} results")
+        assertionResults.addAll(results)
+        if (description != null) {
+            require(assertionResultsByTest[description] == null) {
+                "Test description already contains flicker assertion results."
+            }
+            assertionResultsByTest[description] = results
+        }
+        val aggregatedResults = processFlickerResults(results)
+        collectMetrics(dataRecord, aggregatedResults)
+    }
+
+    private fun processFlickerResults(
+        results: List<IAssertionResult>
+    ): Map<String, AggregatedFlickerResult> {
+        val aggregatedResults = mutableMapOf<String, AggregatedFlickerResult>()
+        for (result in results) {
+            val key = getKeyForAssertionResult(result)
+            if (!aggregatedResults.containsKey(key)) {
+                aggregatedResults[key] = AggregatedFlickerResult()
+            }
+            aggregatedResults[key]!!.addResult(result)
+        }
+        return aggregatedResults
+    }
+
+    private fun collectMetrics(
+        data: DataRecord,
+        aggregatedResults: Map<String, AggregatedFlickerResult>
+    ) {
+        val it = aggregatedResults.entries.iterator()
+
+        while (it.hasNext()) {
+            val (key, result) = it.next()
+            CrossPlatform.log.v(LOG_TAG, "Adding metric ${key}_FAILURES = ${result.failures}")
+            data.addStringMetric("${key}_FAILURES", "${result.failures}")
+        }
+    }
+
+    private fun getKeyForAssertionResult(result: IAssertionResult): String {
+        return "$FAAS_METRICS_PREFIX::${result.assertion.name}"
+    }
+
+    private fun errorReportingBlock(function: () -> Unit) {
+        try {
+            function()
+        } catch (e: Throwable) {
+            CrossPlatform.log.e(FLICKER_TAG, "Error executing in FlickerServiceResultsCollector", e)
+            _executionErrors.add(ExecutionError(e))
+        }
+    }
+
+    override fun testContainsFlicker(description: Description): Boolean {
+        val resultsForTest = resultsForTest(description)
+        return resultsForTest.any { it.failed }
+    }
+
+    override fun resultsForTest(description: Description): List<IAssertionResult> {
+        val resultsForTest = assertionResultsByTest[description]
+        requireNotNull(resultsForTest) { "No results set for test $description" }
+        return resultsForTest
+    }
+
+    companion object {
+        // Unique prefix to add to all FaaS metrics to identify them
+        private const val FAAS_METRICS_PREFIX = "FAAS"
+        private const val LOG_TAG = "$FLICKER_TAG-Collector"
+        private const val WINSCOPE_FILE_PATH_KEY = "winscope_file_path"
+
+        class AggregatedFlickerResult {
+            var failures = 0
+            var passes = 0
+            val errors = mutableListOf<String>()
+            var invocationGroup: AssertionInvocationGroup? = null
+
+            fun addResult(result: IAssertionResult) {
+                if (result.failed) {
+                    failures++
+                    errors.add(result.assertionError?.message ?: "FAILURE WITHOUT ERROR MESSAGE...")
+                } else {
+                    passes++
+                }
+
+                if (invocationGroup == null) {
+                    invocationGroup = result.assertion.stabilityGroup
+                }
+
+                if (invocationGroup != result.assertion.stabilityGroup) {
+                    error("Unexpected assertion group mismatch")
+                }
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/FlickerServiceTracesCollector.kt b/libraries/flicker/src/android/tools/device/flicker/FlickerServiceTracesCollector.kt
new file mode 100644
index 0000000..45d914b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/FlickerServiceTracesCollector.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.device.flicker
+
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.Scenario
+import android.tools.common.ScenarioBuilder
+import android.tools.common.flicker.ITracesCollector
+import android.tools.common.io.IReader
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.io.ResultReaderWithLru
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.monitors.events.EventLogMonitor
+import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
+import android.tools.device.traces.monitors.surfaceflinger.TransactionsTraceMonitor
+import android.tools.device.traces.monitors.wm.TransitionsTraceMonitor
+import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
+import java.io.File
+
+class FlickerServiceTracesCollector(
+    val outputDir: File,
+    val scenario: Scenario =
+        ScenarioBuilder().forClass(FlickerServiceTracesCollector::class.java.simpleName).build()
+) : ITracesCollector {
+
+    private var result: IResultData? = null
+
+    private val traceMonitors =
+        listOf(
+            WindowManagerTraceMonitor(),
+            LayersTraceMonitor(),
+            TransitionsTraceMonitor(),
+            TransactionsTraceMonitor(),
+            EventLogMonitor()
+        )
+
+    override fun start() {
+        reportErrorsBlock("Failed to start traces") {
+            reset()
+            traceMonitors.forEach { it.start() }
+        }
+    }
+
+    override fun stop() {
+        reportErrorsBlock("Failed to stop traces") {
+            CrossPlatform.log.v(LOG_TAG, "Creating output directory for trace files")
+            outputDir.mkdirs()
+
+            CrossPlatform.log.v(LOG_TAG, "Stopping trace monitors")
+            val writer = ResultWriter().forScenario(scenario).withOutputDir(outputDir)
+            traceMonitors.forEach { it.stop(writer) }
+            result = writer.write()
+        }
+    }
+
+    override fun getResultReader(): IReader {
+        return reportErrorsBlock("Failed to get collected traces") {
+            val result = result
+            requireNotNull(result) { "Result not set" }
+            ResultReaderWithLru(result, DEFAULT_TRACE_CONFIG)
+        }
+    }
+
+    private fun reset() {
+        result = null
+        cleanupTraceFiles()
+    }
+
+    /**
+     * Remove the WM trace and layers trace files collected from previous test runs if the directory
+     * exists.
+     */
+    private fun cleanupTraceFiles() {
+        if (outputDir.exists()) {
+            outputDir.deleteRecursively()
+        }
+    }
+
+    private fun <T : Any> reportErrorsBlock(msg: String, block: () -> T): T {
+        try {
+            return block()
+        } catch (e: Throwable) {
+            CrossPlatform.log.e(LOG_TAG, msg, e)
+            throw e
+        }
+    }
+
+    companion object {
+        private const val LOG_TAG = "$FLICKER_TAG-Collector"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/FlickerTag.kt b/libraries/flicker/src/android/tools/device/flicker/FlickerTag.kt
new file mode 100644
index 0000000..19e96ee
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/FlickerTag.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.device.flicker
+
+object FlickerTag {
+    val TRANSITION_START = 89001
+    val TRANSITION_END = 89002
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/IFlickerServiceResultsCollector.kt b/libraries/flicker/src/android/tools/device/flicker/IFlickerServiceResultsCollector.kt
new file mode 100644
index 0000000..a9ef6e1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/IFlickerServiceResultsCollector.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.device.flicker
+
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.device.flicker.legacy.runner.ExecutionError
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+
+interface IFlickerServiceResultsCollector {
+    val executionErrors: List<ExecutionError>
+    fun testStarted(description: Description)
+    fun testFailure(failure: Failure)
+    fun testSkipped(description: Description)
+    fun testFinished(description: Description)
+    fun testContainsFlicker(description: Description): Boolean
+    fun resultsForTest(description: Description): List<IAssertionResult>
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/Utils.kt b/libraries/flicker/src/android/tools/device/flicker/Utils.kt
new file mode 100644
index 0000000..0401402
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/Utils.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.device.flicker
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+object Utils {
+    fun componentMatcherParamsFromName(name: String): Pair<String, String> {
+        var packageName = ""
+        var className = ""
+        if (name.contains("/")) {
+            if (name.contains("#")) {
+                name.removeSuffix("#")
+            }
+            val splitString = name.split('/')
+            packageName = splitString[0]
+            className = splitString[1]
+        } else {
+            className = name
+        }
+        return Pair(packageName, className)
+    }
+
+    fun componentNameMatcherHardcoded(str: String): ComponentNameMatcher? {
+        return when (true) {
+            str.contains("NavigationBar0") -> ComponentNameMatcher.NAV_BAR
+            str.contains("Taskbar") -> ComponentNameMatcher.TASK_BAR
+            str.contains("StatusBar") -> ComponentNameMatcher.STATUS_BAR
+            str.contains("RotationLayer") -> ComponentNameMatcher.ROTATION
+            str.contains("BackColorSurface") -> ComponentNameMatcher.BACK_SURFACE
+            str.contains("InputMethod") -> ComponentNameMatcher.IME
+            str.contains("IME-snapshot-surface") -> ComponentNameMatcher.IME_SNAPSHOT
+            str.contains("Splash Screen") -> ComponentNameMatcher.SPLASH_SCREEN
+            str.contains("SnapshotStartingWindow") -> ComponentNameMatcher.SNAPSHOT
+            str.contains("Letterbox") -> ComponentNameMatcher.LETTERBOX
+            str.contains("Wallpaper BBQ wrapper") -> ComponentNameMatcher.WALLPAPER_BBQ_WRAPPER
+            str.contains("PipContentOverlay") -> ComponentNameMatcher.PIP_CONTENT_OVERLAY
+            str.contains("com.google.android.apps.nexuslauncher") -> ComponentNameMatcher.LAUNCHER
+            str.contains("StageCoordinatorSplitDivider") -> ComponentNameMatcher.SPLIT_DIVIDER
+            else -> null
+        }
+    }
+
+    /**
+     * Obtains the component name matcher corresponding to a name (str) Returns null if the name is
+     * not found in the hardcoded list, and it does not contain both the package and class name
+     * (with a / separator)
+     */
+    fun componentNameMatcherFromName(
+        str: String,
+    ): ComponentNameMatcher? {
+        return try {
+            componentNameMatcherHardcoded(str)
+                ?: ComponentNameMatcher.unflattenFromStringWithJunk(str)
+        } catch (err: IllegalStateException) {
+            null
+        }
+    }
+
+    fun componentNameMatcherToString(componentNameMatcher: ComponentNameMatcher): String {
+        return "ComponentNameMatcher(\"${componentNameMatcher.packageName}\", " +
+            "\"${componentNameMatcher.className}\")"
+    }
+
+    fun componentNameMatcherToStringSimplified(componentNameMatcher: ComponentNameMatcher): String {
+        var className = componentNameMatcher.className
+        val separatedByDots = className.split('.')
+        if (separatedByDots.isNotEmpty()) {
+            className = separatedByDots[separatedByDots.size - 1]
+        }
+        className = className.replace(' ', '_')
+        return className
+    }
+
+    fun componentNameMatcherAsStringFromName(str: String): String? {
+        val componentMatcher = componentNameMatcherFromName(str)
+        return componentMatcher?.componentNameMatcherToString()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/annotation/FlickerServiceCompatible.kt b/libraries/flicker/src/android/tools/device/flicker/annotation/FlickerServiceCompatible.kt
new file mode 100644
index 0000000..2c282d1
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/annotation/FlickerServiceCompatible.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.device.flicker.annotation
+
+/**
+ * Annotate your Flicker test class with this annotation to enable Flicker as a Service on the
+ * transition defined in the Flicker test class. It requires shell transitions to be enabled.
+ */
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FlickerServiceCompatible
diff --git a/libraries/flicker/src/android/tools/device/flicker/assertions/ArtifactAssertionRunner.kt b/libraries/flicker/src/android/tools/device/flicker/assertions/ArtifactAssertionRunner.kt
new file mode 100644
index 0000000..bf585e2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/assertions/ArtifactAssertionRunner.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.io.IReader
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.io.ResultReaderWithLru
+
+/**
+ * Helper class to run an assertion on a flicker artifact
+ *
+ * @param result flicker artifact data
+ * @param resultReader helper class to read the flicker artifact
+ * @param subjectsParser helper class to convert a result into flicker subjects
+ */
+class ArtifactAssertionRunner(
+    private val result: IResultData,
+    resultReader: IReader = ResultReaderWithLru(result, DEFAULT_TRACE_CONFIG),
+    subjectsParser: SubjectsParser = SubjectsParser(resultReader)
+) : BaseAssertionRunner(resultReader, subjectsParser) {
+    override fun doUpdateStatus(newStatus: RunStatus) {
+        result.updateStatus(newStatus)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionDataFactory.kt b/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionDataFactory.kt
new file mode 100644
index 0000000..af8ad2b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionDataFactory.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import kotlin.reflect.KClass
+
+/**
+ * Helper class to create assertions to execute on a trace or state
+ *
+ * @param stateSubject Type of subject used for state assertions
+ * @param traceSubject Type of subject used for trace assertions
+ */
+class AssertionDataFactory(
+    stateSubject: KClass<out FlickerSubject>,
+    private val traceSubject: KClass<out FlickerTraceSubject<*>>
+) : AssertionStateDataFactory(stateSubject) {
+
+    /**
+     * Creates an [assertion] to be executed on trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun createTraceAssertion(
+        assertion: (FlickerTraceSubject<FlickerSubject>) -> Unit
+    ): AssertionData {
+        val closedAssertion: FlickerTraceSubject<FlickerSubject>.() -> Unit = {
+            require(!hasAssertions()) { "Subject was already used to execute assertions" }
+            assertion(this)
+            forAllEntries()
+        }
+        return AssertionData(
+            tag = Tag.ALL,
+            expectedSubjectClass = traceSubject,
+            assertion = closedAssertion as FlickerSubject.() -> Unit
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionStateDataFactory.kt b/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionStateDataFactory.kt
new file mode 100644
index 0000000..518a2f3
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/assertions/AssertionStateDataFactory.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.subject.FlickerSubject
+import kotlin.reflect.KClass
+
+/**
+ * Helper class to create assertions to execute on a state
+ *
+ * @param stateSubject Type of subject used for state assertions
+ */
+open class AssertionStateDataFactory(private val stateSubject: KClass<out FlickerSubject>) {
+    /**
+     * Creates an [assertion] to be executed on the initial state of a trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun createStartStateAssertion(assertion: FlickerSubject.() -> Unit) =
+        AssertionData(tag = Tag.START, expectedSubjectClass = stateSubject, assertion = assertion)
+
+    /**
+     * Creates an [assertion] to be executed on the final state of a trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun createEndStateAssertion(assertion: FlickerSubject.() -> Unit) =
+        AssertionData(tag = Tag.END, expectedSubjectClass = stateSubject, assertion = assertion)
+
+    /**
+     * Creates an [assertion] to be executed on a user defined moment ([tag]) of a trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun createTagAssertion(tag: String, assertion: FlickerSubject.() -> Unit) =
+        AssertionData(tag = tag, expectedSubjectClass = stateSubject, assertion = assertion)
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/assertions/BaseAssertionRunner.kt b/libraries/flicker/src/android/tools/device/flicker/assertions/BaseAssertionRunner.kt
new file mode 100644
index 0000000..4008bfe
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/assertions/BaseAssertionRunner.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.io.IReader
+import android.tools.common.io.RunStatus
+
+/**
+ * Helper class to run an assertions
+ *
+ * @param resultReader helper class to read the flicker artifact
+ * @param subjectsParser helper class to convert a result into flicker subjects
+ */
+abstract class BaseAssertionRunner(
+    private val resultReader: IReader,
+    private val subjectsParser: SubjectsParser = SubjectsParser(resultReader)
+) {
+    /**
+     * Executes [assertion] on the subjects parsed by [subjectsParser] and update its execution
+     * status
+     *
+     * @param assertion to run
+     *
+     * @return the transition execution error (if any) , assertion error (if any), null otherwise
+     */
+    fun runAssertion(assertion: AssertionData): Throwable? {
+        return resultReader.executionError ?: doRunAssertion(assertion)
+    }
+
+    private fun doRunAssertion(assertion: AssertionData): Throwable? {
+        return try {
+            assertion.checkAssertion(subjectsParser)
+            updateResultStatus(error = null)
+            null
+        } catch (error: Throwable) {
+            updateResultStatus(error)
+            FlickerAssertionErrorBuilder()
+                .fromError(error)
+                .atTag(assertion.tag)
+                .withReader(resultReader)
+                .build()
+        }
+    }
+
+    private fun updateResultStatus(error: Throwable?) {
+        val newStatus =
+            if (error == null) RunStatus.ASSERTION_SUCCESS else RunStatus.ASSERTION_FAILED
+
+        if (resultReader.isFailure || resultReader.runStatus == newStatus) return
+
+        doUpdateStatus(newStatus)
+    }
+
+    protected abstract fun doUpdateStatus(newStatus: RunStatus)
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/assertions/FlickerAssertionErrorBuilder.kt b/libraries/flicker/src/android/tools/device/flicker/assertions/FlickerAssertionErrorBuilder.kt
new file mode 100644
index 0000000..b0ffa6e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/assertions/FlickerAssertionErrorBuilder.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerAssertionError
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.common.io.IReader
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+
+class FlickerAssertionErrorBuilder {
+    private var error: Throwable? = null
+    private var artifactPath: String = ""
+    private var tag = ""
+
+    fun fromError(error: Throwable): FlickerAssertionErrorBuilder = apply { this.error = error }
+
+    fun withReader(reader: IReader): FlickerAssertionErrorBuilder = apply {
+        artifactPath = reader.artifactPath
+    }
+
+    fun atTag(_tag: String): FlickerAssertionErrorBuilder = apply {
+        tag =
+            when (_tag) {
+                Tag.START -> "before transition (initial state)"
+                Tag.END -> "after transition (final state)"
+                Tag.ALL -> "during transition"
+                else -> "at user-defined location ($_tag)"
+            }
+    }
+
+    fun build(): FlickerAssertionError {
+        return FlickerAssertionError(errorMessage, rootCause)
+    }
+
+    private val errorMessage
+        get() = buildString {
+            val error = error
+            requireNotNull(error)
+            if (error is FlickerSubjectException) {
+                appendLine(error.message)
+                appendLine()
+                append("\t").appendLine(Fact("Location", tag))
+                appendLine()
+            } else {
+                appendLine(error.message)
+            }
+            append("Trace file:").append(traceFileMessage)
+            appendLine()
+            appendLine("Cause:")
+            append(rootCauseStackTrace)
+            appendLine()
+            appendLine("Full stacktrace:")
+            appendLine()
+        }
+
+    private val traceFileMessage
+        get() = buildString {
+            if (artifactPath.isNotEmpty()) {
+                append("\t")
+                append(artifactPath)
+            }
+        }
+
+    private val rootCauseStackTrace: String
+        get() {
+            val rootCause = rootCause
+            return if (rootCause != null) {
+                val baos = ByteArrayOutputStream()
+                PrintStream(baos, true).use { ps -> rootCause.printStackTrace(ps) }
+                "\t$baos"
+            } else {
+                ""
+            }
+        }
+
+    /**
+     * In some paths the exceptions are encapsulated by the Truth subjects To make sure the correct
+     * error is printed, located the first non-subject related exception and use that as cause.
+     */
+    private val rootCause: Throwable?
+        get() {
+            var childCause: Throwable? = this.error?.cause
+            if (childCause != null && childCause is FlickerSubjectException) {
+                childCause = childCause.cause
+            }
+            return childCause
+        }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/datastore/CachedAssertionRunner.kt b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedAssertionRunner.kt
new file mode 100644
index 0000000..7fb2677
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedAssertionRunner.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.tools.common.IScenario
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.io.RunStatus
+import android.tools.device.flicker.assertions.BaseAssertionRunner
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+
+/**
+ * Helper class to run an assertion on a flicker artifact from a [DataStore]
+ *
+ * @param scenario flicker scenario existing in the [DataStore]
+ * @param resultReader helper class to read the flicker artifact
+ * @param subjectsParser helper class to convert a result into flicker subjects
+ */
+class CachedAssertionRunner(
+    private val scenario: IScenario,
+    resultReader: CachedResultReader = CachedResultReader(scenario, DEFAULT_TRACE_CONFIG),
+    subjectsParser: SubjectsParser = SubjectsParser(resultReader)
+) : BaseAssertionRunner(resultReader, subjectsParser) {
+    override fun doUpdateStatus(newStatus: RunStatus) {
+        val result = DataStore.getResult(scenario)
+        result.updateStatus(newStatus)
+        DataStore.replaceResult(scenario, result)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultReader.kt b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultReader.kt
new file mode 100644
index 0000000..95d0893
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultReader.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.tools.common.IScenario
+import android.tools.common.io.IReader
+import android.tools.device.traces.TraceConfigs
+import android.tools.device.traces.io.ResultReaderWithLru
+
+/** Helper class to read results of a [scenario] from the [DataStore] */
+class CachedResultReader(
+    private val scenario: IScenario,
+    traceConfig: TraceConfigs,
+    private val reader: IReader = ResultReaderWithLru(DataStore.getResult(scenario), traceConfig)
+) : IReader by reader {
+    override fun toString(): String = "$scenario ($reader)"
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultWriter.kt b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultWriter.kt
new file mode 100644
index 0000000..3345a68
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/datastore/CachedResultWriter.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.io.ResultWriter
+
+/** Result writer that adds data of a [scenario] to the [DataStore] */
+class CachedResultWriter : ResultWriter() {
+    override fun write(): IResultData {
+        val result = super.write()
+        DataStore.addResult(scenario, result)
+        return result
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/datastore/DataStore.kt b/libraries/flicker/src/android/tools/device/flicker/datastore/DataStore.kt
new file mode 100644
index 0000000..721aa35
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/datastore/DataStore.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.tools.common.IScenario
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.device.traces.io.IResultData
+import androidx.annotation.VisibleForTesting
+
+/** In memory data store for flicker transitions, assertions and results */
+object DataStore {
+    private val cachedResults = mutableMapOf<IScenario, IResultData>()
+    private val cachedFlickerServiceAssertions = mutableMapOf<IScenario, List<IAssertionResult>>()
+
+    @VisibleForTesting
+    fun clear() {
+        cachedResults.clear()
+        cachedFlickerServiceAssertions.clear()
+    }
+
+    /** @return if the store has results for [scenario] */
+    fun containsResult(scenario: IScenario): Boolean = cachedResults.containsKey(scenario)
+
+    /**
+     * Adds [result] to the store with [scenario] as id
+     *
+     * @throws IllegalStateException is [scenario] already exists in the data store
+     */
+    @Throws(IllegalStateException::class)
+    fun addResult(scenario: IScenario, result: IResultData) {
+        if (containsResult(scenario)) {
+            error("Result for $scenario already in data store")
+        }
+        cachedResults[scenario] = result
+    }
+
+    /**
+     * Replaces the old value [scenario] result in the store by [newResult]
+     *
+     * @throws IllegalStateException is [scenario] doesn't exist in the data store
+     */
+    @Throws(IllegalStateException::class)
+    fun replaceResult(scenario: IScenario, newResult: IResultData) {
+        if (!containsResult(scenario)) {
+            error("Result for $scenario not in data store")
+        }
+        cachedResults[scenario] = newResult
+    }
+
+    /**
+     * @return the result for [scenario]
+     *
+     * @throws IllegalStateException is [scenario] doesn't exist in the data store
+     */
+    @Throws(IllegalStateException::class)
+    fun getResult(scenario: IScenario): IResultData =
+        cachedResults[scenario] ?: error("No value for $scenario")
+
+    /** @return if the store has results for [scenario] */
+    fun containsFlickerServiceResult(scenario: IScenario): Boolean =
+        cachedFlickerServiceAssertions.containsKey(scenario)
+
+    fun addFlickerServiceResults(scenario: IScenario, results: List<IAssertionResult>) {
+        if (containsFlickerServiceResult(scenario)) {
+            error("Result for $scenario already in data store")
+        }
+        cachedFlickerServiceAssertions[scenario] = results
+    }
+
+    fun getFlickerServiceResults(scenario: IScenario): List<IAssertionResult> {
+        return cachedFlickerServiceAssertions[scenario]
+            ?: error("No flicker service results for $scenario")
+    }
+
+    fun getFlickerServiceResultsForAssertion(
+        scenario: IScenario,
+        assertionName: String
+    ): List<IAssertionResult> {
+        return cachedFlickerServiceAssertions[scenario]?.filter {
+            it.assertion.name == assertionName
+        }
+            ?: error("Assertion with name $assertionName not found for scenario $scenario")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/AbstractFlickerRunnerDecorator.kt b/libraries/flicker/src/android/tools/device/flicker/junit/AbstractFlickerRunnerDecorator.kt
new file mode 100644
index 0000000..6d4082e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/AbstractFlickerRunnerDecorator.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.device.flicker.junit
+
+import android.app.Instrumentation
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.Scenario
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.runner.TransitionRunner
+import androidx.test.platform.app.InstrumentationRegistry
+import java.lang.reflect.Modifier
+import org.junit.runner.Description
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.TestClass
+
+abstract class AbstractFlickerRunnerDecorator(
+    protected val testClass: TestClass,
+    protected val scenario: Scenario?,
+    protected val inner: IFlickerJUnitDecorator?
+) : IFlickerJUnitDecorator {
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    final override fun doValidateConstructor(): List<Throwable> {
+        val errors = internalDoValidateConstructor().toMutableList()
+        if (errors.isEmpty()) {
+            inner?.doValidateConstructor()?.let { errors.addAll(it) }
+        }
+        return errors
+    }
+
+    final override fun doValidateInstanceMethods(): List<Throwable> {
+        val errors = internalDoValidateInstanceMethods().toMutableList()
+        if (errors.isEmpty()) {
+            inner?.doValidateInstanceMethods()?.let { errors.addAll(it) }
+        }
+        return errors
+    }
+
+    private fun internalDoValidateConstructor(): List<Throwable> {
+        val errors = mutableListOf<Throwable>()
+        val ctor = testClass.javaClass.constructors.firstOrNull()
+        if (ctor?.parameterTypes?.none { it == FlickerTest::class.java } != false) {
+            errors.add(
+                IllegalStateException(
+                    "Constructor should have a parameter of type " +
+                        FlickerTest::class.java.simpleName
+                )
+            )
+        }
+        return errors
+    }
+
+    /** Validate that the test has one method annotated with [FlickerBuilderProvider] */
+    private fun internalDoValidateInstanceMethods(): List<Throwable> {
+        val errors = mutableListOf<Throwable>()
+        val methods = getCandidateProviderMethods(testClass)
+
+        if (methods.isEmpty() || methods.size > 1) {
+            val prefix = if (methods.isEmpty()) "One" else "Only one"
+            errors.add(
+                IllegalArgumentException(
+                    "$prefix object should be annotated with @FlickerBuilderProvider"
+                )
+            )
+        } else {
+            val method = methods.first()
+
+            if (Modifier.isStatic(method.method.modifiers)) {
+                errors.add(IllegalArgumentException("Method ${method.name}() should not be static"))
+            }
+            if (!Modifier.isPublic(method.method.modifiers)) {
+                errors.add(IllegalArgumentException("Method ${method.name}() should be public"))
+            }
+            if (method.returnType != FlickerBuilder::class.java) {
+                errors.add(
+                    IllegalArgumentException(
+                        "Method ${method.name}() should return a " +
+                            "${FlickerBuilder::class.java.simpleName} object"
+                    )
+                )
+            }
+            if (method.method.parameterTypes.isNotEmpty()) {
+                errors.add(
+                    IllegalArgumentException("Method ${method.name} should have no parameters")
+                )
+            }
+        }
+
+        return errors
+    }
+
+    protected fun doRunTransition(test: Any, description: Description?) {
+        CrossPlatform.log.d(FLICKER_TAG, "$scenario - Setting up FaaS")
+        val scenario = scenario
+        requireNotNull(scenario) { "Expected to have a scenario to run" }
+        if (!DataStore.containsResult(scenario)) {
+            CrossPlatform.log.v(FLICKER_TAG, "Creating flicker object for $scenario")
+            val builder = getFlickerBuilder(test)
+            CrossPlatform.log.v(FLICKER_TAG, "Creating flicker object for $scenario")
+            val flicker = builder.build()
+            val runner = TransitionRunner(scenario, instrumentation)
+            CrossPlatform.log.v(FLICKER_TAG, "Running transition for $scenario")
+            runner.execute(flicker, description)
+        }
+    }
+
+    private val providerMethod: FrameworkMethod
+        get() =
+            getCandidateProviderMethods(testClass).firstOrNull()
+                ?: error("Provider method not found")
+
+    private fun getFlickerBuilder(test: Any): FlickerBuilder {
+        CrossPlatform.log.v(FLICKER_TAG, "Obtaining flicker builder for $testClass")
+        return providerMethod.invokeExplosively(test) as FlickerBuilder
+    }
+
+    companion object {
+        private fun getCandidateProviderMethods(testClass: TestClass): List<FrameworkMethod> =
+            testClass.getAnnotatedMethods(FlickerBuilderProvider::class.java) ?: emptyList()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunner.kt b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunner.kt
new file mode 100644
index 0000000..033231b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunner.kt
@@ -0,0 +1,366 @@
+/*
+ * 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.device.flicker.junit
+
+import android.os.Bundle
+import android.platform.test.util.TestFilter
+import android.tools.common.Scenario
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.Collections
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.internal.AssumptionViolatedException
+import org.junit.internal.runners.model.EachTestNotifier
+import org.junit.runner.Description
+import org.junit.runner.manipulation.Filter
+import org.junit.runner.manipulation.InvalidOrderingException
+import org.junit.runner.manipulation.NoTestsRemainException
+import org.junit.runner.manipulation.Orderable
+import org.junit.runner.manipulation.Orderer
+import org.junit.runner.manipulation.Sorter
+import org.junit.runner.notification.RunNotifier
+import org.junit.runner.notification.StoppedByUserException
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.RunnerScheduler
+import org.junit.runners.model.Statement
+import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters
+import org.junit.runners.parameterized.TestWithParameters
+
+/**
+ * Implements the JUnit 4 standard test case class model, parsing from a flicker DSL.
+ *
+ * Supports both assertions in {@link org.junit.Test} and assertions defined in the DSL
+ *
+ * When using this runner the default `atest class#method` command doesn't work. Instead use: --
+ * --test-arg \
+ * ```
+ *     com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests:=<TEST_NAME>
+ * ```
+ * For example: `atest FlickerTests -- \
+ * ```
+ *     --test-arg com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests\
+ *     :=com.android.server.wm.flicker.close.\
+ *     CloseAppBackButtonTest#launcherWindowBecomesVisible[ROTATION_90_GESTURAL_NAV]`
+ * ```
+ */
+class FlickerBlockJUnit4ClassRunner(test: TestWithParameters?, private val scenario: Scenario?) :
+    BlockJUnit4ClassRunnerWithParameters(test), IFlickerJUnitDecorator {
+
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val flickerDecorator =
+        test?.let {
+            FlickerServiceDecorator(
+                test.testClass,
+                scenario,
+                inner = LegacyFlickerDecorator(test.testClass, scenario, inner = this)
+            )
+        }
+
+    override fun run(notifier: RunNotifier) {
+        val testNotifier = EachTestNotifier(notifier, description)
+        testNotifier.fireTestSuiteStarted()
+        try {
+            val statement = classBlock(notifier)
+            statement.evaluate()
+        } catch (e: AssumptionViolatedException) {
+            testNotifier.addFailedAssumption(e)
+        } catch (e: StoppedByUserException) {
+            throw e
+        } catch (e: Throwable) {
+            testNotifier.addFailure(e)
+        } finally {
+            testNotifier.fireTestSuiteFinished()
+        }
+    }
+
+    /**
+     * Implementation of Filterable and Sortable Based on JUnit's ParentRunner implementation but
+     * with a minor modification to ensure injected FaaS tests are not filtered out.
+     */
+    @Throws(NoTestsRemainException::class)
+    override fun filter(filter: Filter) {
+        childrenLock.lock()
+        try {
+            val children: MutableList<FrameworkMethod> = getFilteredChildren().toMutableList()
+            val iter: MutableIterator<FrameworkMethod> = children.iterator()
+            while (iter.hasNext()) {
+                val each: FrameworkMethod = iter.next()
+                if (isInjectedFaasTest(each)) {
+                    // Don't filter out injected FaaS tests
+                    continue
+                }
+                if (shouldRun(filter, each)) {
+                    try {
+                        filter.apply(each)
+                    } catch (e: NoTestsRemainException) {
+                        iter.remove()
+                    }
+                } else {
+                    iter.remove()
+                }
+            }
+            filteredChildren = Collections.unmodifiableList(children)
+            if (filteredChildren!!.isEmpty()) {
+                throw NoTestsRemainException()
+            }
+        } finally {
+            childrenLock.unlock()
+        }
+    }
+
+    private fun isInjectedFaasTest(method: FrameworkMethod): Boolean {
+        return method is FlickerServiceCachedTestCase
+    }
+
+    override fun isIgnored(child: FrameworkMethod): Boolean {
+        return child.getAnnotation(Ignore::class.java) != null
+    }
+
+    /**
+     * Returns the methods that run tests. Is ran after validateInstanceMethods, so
+     * flickerBuilderProviderMethod should be set.
+     */
+    public override fun computeTestMethods(): List<FrameworkMethod> {
+        val result = mutableListOf<FrameworkMethod>()
+        if (scenario != null) {
+            val testInstance = createTest()
+            result.addAll(flickerDecorator?.getTestMethods(testInstance) ?: emptyList())
+        }
+        return result
+    }
+
+    override fun describeChild(method: FrameworkMethod?): Description {
+        return flickerDecorator?.getChildDescription(method)
+            ?: error("There are no children to describe")
+    }
+
+    /** {@inheritDoc} */
+    override fun getChildren(): MutableList<FrameworkMethod> {
+        val validChildren =
+            super.getChildren().filter {
+                val childDescription = describeChild(it)
+                TestFilter.isFilteredOrUnspecified(arguments, childDescription)
+            }
+        return validChildren.toMutableList()
+    }
+
+    override fun methodInvoker(method: FrameworkMethod, test: Any): Statement {
+        return flickerDecorator?.getMethodInvoker(method, test)
+            ?: error("No statements to invoke for $method in $test")
+    }
+
+    override fun validateConstructor(errors: MutableList<Throwable>) {
+        super.validateConstructor(errors)
+
+        if (errors.isEmpty()) {
+            flickerDecorator?.doValidateConstructor()?.let { errors.addAll(it) }
+        }
+    }
+
+    @Deprecated("Deprecated in Java")
+    override fun validateInstanceMethods(errors: MutableList<Throwable>?) {
+        flickerDecorator?.doValidateInstanceMethods()?.let { errors?.addAll(it) }
+    }
+
+    /** IFlickerJunitDecorator implementation */
+    override fun getTestMethods(test: Any): List<FrameworkMethod> {
+        val tests = mutableListOf<FrameworkMethod>()
+        tests.addAll(super.computeTestMethods())
+        return tests
+    }
+
+    override fun getChildDescription(method: FrameworkMethod?): Description? {
+        return super.describeChild(method)
+    }
+
+    override fun doValidateInstanceMethods(): List<Throwable> {
+        val errors = mutableListOf<Throwable>()
+        super.validateInstanceMethods(errors)
+        return errors
+    }
+
+    override fun doValidateConstructor(): List<Throwable> {
+        val result = mutableListOf<Throwable>()
+        super.validateConstructor(result)
+        return result
+    }
+
+    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
+        return super.methodInvoker(method, test)
+    }
+
+    /**
+     * ********************************************************************************************
+     * START of code copied from ParentRunner to have local access to filteredChildren to ensure
+     * FaaS injected tests are not filtered out.
+     */
+
+    // Guarded by childrenLock
+    @Volatile private var filteredChildren: List<FrameworkMethod>? = null
+    private val childrenLock: Lock = ReentrantLock()
+
+    @Volatile
+    private var scheduler: RunnerScheduler =
+        object : RunnerScheduler {
+            override fun schedule(childStatement: Runnable) {
+                childStatement.run()
+            }
+
+            override fun finished() {
+                // do nothing
+            }
+        }
+
+    /**
+     * Sets a scheduler that determines the order and parallelization of children. Highly
+     * experimental feature that may change.
+     */
+    override fun setScheduler(scheduler: RunnerScheduler) {
+        this.scheduler = scheduler
+    }
+
+    private fun shouldRun(filter: Filter, each: FrameworkMethod): Boolean {
+        return filter.shouldRun(describeChild(each))
+    }
+
+    override fun sort(sorter: Sorter) {
+        if (shouldNotReorder()) {
+            return
+        }
+        childrenLock.lock()
+        filteredChildren =
+            try {
+                for (each in getFilteredChildren()) {
+                    sorter.apply(each)
+                }
+                val sortedChildren: List<FrameworkMethod> =
+                    ArrayList<FrameworkMethod>(getFilteredChildren())
+                Collections.sort(sortedChildren, comparator(sorter))
+                Collections.unmodifiableList(sortedChildren)
+            } finally {
+                childrenLock.unlock()
+            }
+    }
+
+    /**
+     * Implementation of [Orderable.order].
+     *
+     * @since 4.13
+     */
+    @Throws(InvalidOrderingException::class)
+    override fun order(orderer: Orderer) {
+        if (shouldNotReorder()) {
+            return
+        }
+        childrenLock.lock()
+        try {
+            var children: List<FrameworkMethod> = getFilteredChildren()
+            // In theory, we could have duplicate Descriptions. De-dup them before ordering,
+            // and add them back at the end.
+            val childMap: MutableMap<Description, MutableList<FrameworkMethod>> =
+                LinkedHashMap(children.size)
+            for (child in children) {
+                val description = describeChild(child)
+                var childrenWithDescription: MutableList<FrameworkMethod>? = childMap[description]
+                if (childrenWithDescription == null) {
+                    childrenWithDescription = ArrayList<FrameworkMethod>(1)
+                    childMap[description] = childrenWithDescription
+                }
+                childrenWithDescription.add(child)
+                orderer.apply(child)
+            }
+            val inOrder = orderer.order(childMap.keys)
+            children = ArrayList<FrameworkMethod>(children.size)
+            for (description in inOrder) {
+                children.addAll(childMap[description]!!)
+            }
+            filteredChildren = Collections.unmodifiableList(children)
+        } finally {
+            childrenLock.unlock()
+        }
+    }
+
+    private fun shouldNotReorder(): Boolean {
+        // If the test specifies a specific order, do not reorder.
+        return description.getAnnotation(FixMethodOrder::class.java) != null
+    }
+
+    private fun getFilteredChildren(): List<FrameworkMethod> {
+        childrenLock.lock()
+        val filteredChildren =
+            try {
+                if (filteredChildren != null) {
+                    filteredChildren!!
+                } else {
+                    Collections.unmodifiableList(ArrayList<FrameworkMethod>(children))
+                }
+            } finally {
+                childrenLock.unlock()
+            }
+        return filteredChildren
+    }
+
+    override fun getDescription(): Description {
+        val clazz = testClass.javaClass
+        // if subclass overrides `getName()` then we should use it
+        // to maintain backwards compatibility with JUnit 4.12
+        val description: Description =
+            if (clazz == null || clazz.name != name) {
+                Description.createSuiteDescription(name, *runnerAnnotations)
+            } else {
+                Description.createSuiteDescription(clazz, *runnerAnnotations)
+            }
+        for (child in getFilteredChildren()) {
+            description.addChild(describeChild(child))
+        }
+        return description
+    }
+
+    /**
+     * Returns a [Statement]: Call [.runChild] on each object returned by [.getChildren] (subject to
+     * any imposed filter and sort)
+     */
+    override fun childrenInvoker(notifier: RunNotifier): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                runChildren(notifier)
+            }
+        }
+    }
+
+    private fun runChildren(notifier: RunNotifier) {
+        val currentScheduler = scheduler
+        try {
+            for (each in getFilteredChildren()) {
+                currentScheduler.schedule { this.runChild(each, notifier) }
+            }
+        } finally {
+            currentScheduler.finished()
+        }
+    }
+
+    private fun comparator(sorter: Sorter): Comparator<in FrameworkMethod> {
+        return Comparator { o1, o2 -> sorter.compare(describeChild(o1), describeChild(o2)) }
+    }
+
+    /**
+     * END of code copied from ParentRunner to have local access to filteredChildren to ensure FaaS
+     * injected tests are not filtered out.
+     */
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBuilderProvider.kt b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBuilderProvider.kt
new file mode 100644
index 0000000..c4ce421
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerBuilderProvider.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.device.flicker.junit
+
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+@Target(
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY_GETTER,
+    AnnotationTarget.PROPERTY_SETTER
+)
+annotation class FlickerBuilderProvider
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/FlickerParametersRunnerFactory.kt b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerParametersRunnerFactory.kt
new file mode 100644
index 0000000..b45f7b2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerParametersRunnerFactory.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.device.flicker.junit
+
+import android.tools.common.CrossPlatform
+import android.tools.common.TimestampFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.traces.ANDROID_LOGGER
+import android.tools.device.traces.formatRealTimestamp
+import org.junit.runner.Runner
+import org.junit.runners.parameterized.ParametersRunnerFactory
+import org.junit.runners.parameterized.TestWithParameters
+
+/**
+ * A {@code FlickerRunnerFactory} creates a runner for a single {@link TestWithParameters}. Parses
+ * and executes assertions from a flicker DSL
+ */
+class FlickerParametersRunnerFactory : ParametersRunnerFactory {
+    init {
+        CrossPlatform.setLogger(ANDROID_LOGGER)
+            .setTimestampFactory(TimestampFactory { formatRealTimestamp(it) })
+    }
+
+    override fun createRunnerForTestWithParameters(test: TestWithParameters): Runner {
+        val simpleClassName = test.testClass.javaClass.simpleName
+        val flickerTest =
+            test.parameters.filterIsInstance<FlickerTest>().firstOrNull()
+                ?: error(
+                    "Unable to extract ${FlickerTest::class.simpleName} for class $simpleClassName"
+                )
+        val scenario = flickerTest.initialize(simpleClassName)
+        val newTest =
+            TestWithParameters(
+                /*name */ "[${scenario.description}]",
+                /* testClass */ test.testClass,
+                /* parameters */ test.parameters
+            )
+        return FlickerBlockJUnit4ClassRunner(newTest, scenario)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceCachedTestCase.kt b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceCachedTestCase.kt
new file mode 100644
index 0000000..ff9c261
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceCachedTestCase.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.device.flicker.junit
+
+import android.tools.common.Cache
+import android.tools.common.IScenario
+import android.tools.common.flicker.AssertionInvocationGroup
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.device.flicker.FlickerServiceResultsCollector
+import android.tools.device.flicker.datastore.DataStore
+import java.lang.reflect.Method
+import org.junit.Assume
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+import org.junit.runners.model.FrameworkMethod
+
+class FlickerServiceCachedTestCase(
+    method: Method,
+    scenario: IScenario,
+    internal val assertionName: String,
+    private val onlyBlocking: Boolean,
+    private val metricsCollector: FlickerServiceResultsCollector?,
+    private val isLast: Boolean
+) : FrameworkMethod(method) {
+    private val fullResults =
+        DataStore.getFlickerServiceResultsForAssertion(scenario, assertionName)
+    private val results: List<IAssertionResult>
+        get() =
+            fullResults.filter {
+                !onlyBlocking || it.assertion.stabilityGroup == AssertionInvocationGroup.BLOCKING
+            }
+
+    override fun invokeExplosively(target: Any?, vararg params: Any?): Any {
+        error("Shouldn't have reached here")
+    }
+
+    override fun getName(): String = "FaaS_$assertionName"
+
+    fun execute(description: Description) {
+        val results = results
+
+        if (isLast) {
+            metricsCollector?.testStarted(description)
+        }
+        try {
+            Assume.assumeFalse(results.isEmpty())
+            results.firstOrNull { it.assertionError != null }?.assertionError?.let { throw it }
+        } catch (e: Throwable) {
+            metricsCollector?.testFailure(Failure(description, e))
+            throw e
+        } finally {
+            if (isLast) {
+                Cache.clear()
+                metricsCollector?.testFinished(description)
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceDecorator.kt b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceDecorator.kt
new file mode 100644
index 0000000..e3ea1e2
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/FlickerServiceDecorator.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.device.flicker.junit
+
+import android.os.Bundle
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.Scenario
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.device.flicker.FlickerService
+import android.tools.device.flicker.FlickerServiceResultsCollector
+import android.tools.device.flicker.IS_FAAS_ENABLED
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.runner.Description
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+import org.junit.runners.model.TestClass
+
+class FlickerServiceDecorator(
+    testClass: TestClass,
+    scenario: Scenario?,
+    inner: IFlickerJUnitDecorator?
+) : AbstractFlickerRunnerDecorator(testClass, scenario, inner) {
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val flickerService = FlickerService()
+    private val metricsCollector: FlickerServiceResultsCollector? =
+        scenario?.let {
+            FlickerServiceResultsCollector(
+                LegacyFlickerTraceCollector(scenario),
+                collectMetricsPerTest = true
+            )
+        }
+
+    private val onlyBlocking
+        get() =
+            scenario?.getConfigValue<Boolean>(Scenario.FAAS_BLOCKING)
+                ?: arguments.getString(Scenario.FAAS_BLOCKING).toBoolean()
+
+    private val isClassFlickerServiceCompatible: Boolean
+        get() =
+            testClass.annotations.filterIsInstance<FlickerServiceCompatible>().firstOrNull() != null
+
+    override fun getChildDescription(method: FrameworkMethod?): Description? {
+        requireNotNull(scenario) { "Expected to have a scenario to run" }
+        return if (method is FlickerServiceCachedTestCase) {
+            Description.createTestDescription(
+                testClass.javaClass,
+                "${method.name}[${scenario.description}]",
+                *method.getAnnotations()
+            )
+        } else {
+            inner?.getChildDescription(method)
+        }
+    }
+
+    override fun getTestMethods(test: Any): List<FrameworkMethod> {
+        val result = inner?.getTestMethods(test)?.toMutableList() ?: mutableListOf()
+        if (shouldComputeTestMethods()) {
+            CrossPlatform.log.withTracing(
+                "$FAAS_METRICS_PREFIX getTestMethods ${testClass.javaClass.simpleName}"
+            ) {
+                result.addAll(computeFlickerServiceTests(test))
+                CrossPlatform.log.d(FLICKER_TAG, "Computed ${result.size} flicker tests")
+            }
+        }
+        return result
+    }
+
+    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                if (method is FlickerServiceCachedTestCase) {
+                    val description = getChildDescription(method) ?: error("Missing description")
+                    method.execute(description)
+                } else {
+                    inner?.getMethodInvoker(method, test)?.evaluate()
+                }
+            }
+        }
+    }
+
+    private fun shouldComputeTestMethods(): Boolean {
+        // Don't compute when called from validateInstanceMethods since this will fail
+        // as the parameters will not be set. And AndroidLogOnlyBuilder is a non-executing runner
+        // used to run tests in dry-run mode, so we don't want to execute in flicker transition in
+        // that case either.
+        val stackTrace = Thread.currentThread().stackTrace
+        val isDryRun =
+            stackTrace.any { it.methodName == "validateInstanceMethods" } ||
+                stackTrace.any {
+                    it.className == "androidx.test.internal.runner.AndroidLogOnlyBuilder"
+                } ||
+                stackTrace.any {
+                    it.className == "androidx.test.internal.runner.NonExecutingRunner"
+                }
+
+        val filters = getFiltersFromArguments()
+        // a method is filtered out if there's a filter and the filter doesn't include it's class
+        // or if the filter includes its class, but it's not flicker as a service
+        val isFilteredOut =
+            filters.isNotEmpty() && !(filters[testClass.javaClass.simpleName] ?: false)
+
+        return IS_FAAS_ENABLED &&
+            isShellTransitionsEnabled &&
+            isClassFlickerServiceCompatible &&
+            !isFilteredOut &&
+            !isDryRun
+    }
+
+    private fun getFiltersFromArguments(): Map<String, Boolean> {
+        val testFilters = arguments.getString(OPTION_NAME) ?: return emptyMap()
+        val result = mutableMapOf<String, Boolean>()
+
+        // Test the display name against all filter arguments.
+        for (testFilter in testFilters.split(",")) {
+            val filterComponents = testFilter.split("#")
+            if (filterComponents.size != 2) {
+                CrossPlatform.log.e(
+                    LOG_TAG,
+                    "Invalid filter-tests instrumentation argument supplied, $testFilter."
+                )
+                continue
+            }
+            val methodName = filterComponents[1]
+            val className = filterComponents[0]
+            result[className] = methodName.startsWith(FAAS_METRICS_PREFIX)
+        }
+
+        return result
+    }
+
+    /**
+     * Runs the flicker transition to collect the traces and run FaaS on them to get the FaaS
+     * results and then create functional test results for each of them.
+     */
+    private fun computeFlickerServiceTests(test: Any): List<FrameworkMethod> {
+        requireNotNull(scenario) { "Expected to have a scenario to run" }
+        if (!DataStore.containsFlickerServiceResult(scenario)) {
+            this.doRunFlickerService(test)
+        }
+        val aggregateResults =
+            DataStore.getFlickerServiceResults(scenario).groupBy { it.assertion.name }
+
+        val cachedResultMethod =
+            FlickerServiceCachedTestCase::class.java.getMethod("execute", Description::class.java)
+        return aggregateResults.keys.mapIndexed { idx, value ->
+            FlickerServiceCachedTestCase(
+                cachedResultMethod,
+                scenario,
+                value,
+                onlyBlocking,
+                metricsCollector,
+                isLast = aggregateResults.keys.size == idx
+            )
+        }
+    }
+
+    private fun doRunFlickerService(test: Any): List<IAssertionResult> {
+        requireNotNull(scenario) { "Expected to have a scenario to run" }
+        val description =
+            Description.createTestDescription(
+                this::class.java.simpleName,
+                "computeFlickerServiceTests"
+            )
+        this.doRunTransition(test, description)
+
+        val reader = CachedResultReader(scenario, DEFAULT_TRACE_CONFIG)
+        val results = flickerService.process(reader)
+
+        DataStore.addFlickerServiceResults(scenario, results)
+        return results
+    }
+
+    companion object {
+        private const val FAAS_METRICS_PREFIX = "FAAS"
+        private const val OPTION_NAME = "filter-tests"
+        private val LOG_TAG = FlickerServiceDecorator::class.java.simpleName
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/IFlickerJUnitDecorator.kt b/libraries/flicker/src/android/tools/device/flicker/junit/IFlickerJUnitDecorator.kt
new file mode 100644
index 0000000..b5fe793
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/IFlickerJUnitDecorator.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.device.flicker.junit
+
+import org.junit.runner.Description
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+interface IFlickerJUnitDecorator {
+    fun getChildDescription(method: FrameworkMethod?): Description?
+
+    /** Returns the methods that run tests. */
+    fun getTestMethods(test: Any): List<FrameworkMethod>
+
+    fun doValidateInstanceMethods(): List<Throwable>
+
+    fun doValidateConstructor(): List<Throwable>
+
+    /** @return a [Statement] to execute a flicker service assertion */
+    fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerDecorator.kt b/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerDecorator.kt
new file mode 100644
index 0000000..49ea6e0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerDecorator.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.device.flicker.junit
+
+import android.tools.common.Scenario
+import org.junit.runner.Description
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+import org.junit.runners.model.TestClass
+
+class LegacyFlickerDecorator(
+    testClass: TestClass,
+    scenario: Scenario?,
+    inner: IFlickerJUnitDecorator?
+) : AbstractFlickerRunnerDecorator(testClass, scenario, inner) {
+    override fun getChildDescription(method: FrameworkMethod?): Description? {
+        return inner?.getChildDescription(method)
+    }
+
+    override fun getTestMethods(test: Any): List<FrameworkMethod> {
+        return inner?.getTestMethods(test) ?: emptyList()
+    }
+
+    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                val description = getChildDescription(method)
+                doRunTransition(test, description)
+                inner?.getMethodInvoker(method, test)?.evaluate()
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerTraceCollector.kt b/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerTraceCollector.kt
new file mode 100644
index 0000000..775941e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/junit/LegacyFlickerTraceCollector.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.device.flicker.junit
+
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.flicker.ITracesCollector
+import android.tools.common.io.IReader
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+
+class LegacyFlickerTraceCollector(private val scenario: IScenario) : ITracesCollector {
+    override fun start() {}
+
+    override fun stop() {}
+
+    override fun getResultReader(): IReader {
+        CrossPlatform.log.d("FAAS", "LegacyFlickerTraceCollector#getCollectedTraces")
+        return CachedResultReader(scenario, DEFAULT_TRACE_CONFIG)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/AbstractFlickerTestData.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/AbstractFlickerTestData.kt
new file mode 100644
index 0000000..db4a43f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/AbstractFlickerTestData.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.device.flicker.legacy
+
+abstract class AbstractFlickerTestData : IFlickerTestData {
+    private var assertionsCheckedCallback: ((Boolean) -> Unit)? = null
+    private var createTagCallback: (String) -> Unit = {}
+
+    final override fun withTag(tag: String, commands: IFlickerTestData.() -> Any) {
+        commands()
+        createTagCallback(tag)
+    }
+
+    final override fun createTag(tag: String) {
+        withTag(tag) {}
+    }
+
+    final override fun clearTagListener() {
+        assertionsCheckedCallback = {}
+    }
+
+    final override fun setAssertionsCheckedCallback(callback: (Boolean) -> Unit) {
+        assertionsCheckedCallback = callback
+    }
+
+    final override fun setCreateTagListener(callback: (String) -> Unit) {
+        createTagCallback = callback
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerBuilder.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerBuilder.kt
new file mode 100644
index 0000000..8bf0578
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerBuilder.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.app.Instrumentation
+import android.tools.common.io.TraceType
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.monitors.NoTraceMonitor
+import android.tools.device.traces.monitors.ScreenRecorder
+import android.tools.device.traces.monitors.events.EventLogMonitor
+import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
+import android.tools.device.traces.monitors.surfaceflinger.TransactionsTraceMonitor
+import android.tools.device.traces.monitors.wm.TransitionsTraceMonitor
+import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.uiautomator.UiDevice
+import java.io.File
+
+/** Build Flicker tests using Flicker DSL */
+@FlickerDslMarker
+class FlickerBuilder(
+    private val instrumentation: Instrumentation,
+    private val outputDir: File = getDefaultFlickerOutputDir(),
+    private val wmHelper: WindowManagerStateHelper =
+        WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false),
+    private val setupCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
+    private val transitionCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
+    private val teardownCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
+    val device: UiDevice = UiDevice.getInstance(instrumentation),
+    private val traceMonitors: MutableList<ITransitionMonitor> =
+        mutableListOf<ITransitionMonitor>().also {
+            it.add(WindowManagerTraceMonitor())
+            it.add(LayersTraceMonitor())
+            if (isShellTransitionsEnabled) {
+                // Transition tracing only works if shell transitions are enabled.
+                it.add(TransitionsTraceMonitor())
+            }
+            it.add(TransactionsTraceMonitor())
+            it.add(ScreenRecorder(instrumentation.targetContext))
+            it.add(EventLogMonitor())
+        }
+) {
+    private var usingExistingTraces = false
+
+    /**
+     * Configure a [WindowManagerTraceMonitor] to obtain [WindowManagerTrace]
+     *
+     * By default, the tracing is always active. To disable tracing return null
+     *
+     * If this tracing is disabled, the assertions for [WindowManagerTrace] and [WindowManagerState]
+     * will not be executed
+     */
+    fun withWindowManagerTracing(traceMonitor: () -> WindowManagerTraceMonitor?): FlickerBuilder =
+        apply {
+            traceMonitors.removeIf { it is WindowManagerTraceMonitor }
+            addMonitor(traceMonitor())
+        }
+
+    /** Disable [LayersTraceMonitor]. */
+    fun withoutLayerTracing(): FlickerBuilder = apply { withLayerTracing { null } }
+
+    /**
+     * Configure a [LayersTraceMonitor] to obtain [LayersTrace].
+     *
+     * By default the tracing is always active. To disable tracing return null
+     *
+     * If this tracing is disabled, the assertions for [LayersTrace] and [LayerTraceEntry] will not
+     * be executed
+     */
+    fun withLayerTracing(traceMonitor: () -> LayersTraceMonitor?): FlickerBuilder = apply {
+        traceMonitors.removeIf { it is LayersTraceMonitor }
+        addMonitor(traceMonitor())
+    }
+
+    /** Disable [TransitionsTraceMonitor]. */
+    fun withoutTransitionTracing(): FlickerBuilder = apply { withTransitionTracing { null } }
+
+    /**
+     * Configure a [TransitionsTraceMonitor] to obtain [TransitionsTrace].
+     *
+     * By default, shell transition tracing is disabled.
+     */
+    fun withTransitionTracing(traceMonitor: () -> TransitionsTraceMonitor?): FlickerBuilder =
+        apply {
+            traceMonitors.removeIf { it is TransitionsTraceMonitor }
+            addMonitor(traceMonitor())
+        }
+
+    /** Disable [TransactionsTraceMonitor]. */
+    fun withoutTransactionsTracing(): FlickerBuilder = apply { withTransactionsTracing { null } }
+
+    /**
+     * Configure a [TransactionsTraceMonitor] to obtain [TransactionsTrace].
+     *
+     * By default, shell transition tracing is disabled.
+     */
+    fun withTransactionsTracing(traceMonitor: () -> TransactionsTraceMonitor?): FlickerBuilder =
+        apply {
+            traceMonitors.removeIf { it is TransactionsTraceMonitor }
+            addMonitor(traceMonitor())
+        }
+
+    /**
+     * Configure a [ScreenRecorder].
+     *
+     * By default, the tracing is always active. To disable tracing return null
+     */
+    fun withScreenRecorder(screenRecorder: () -> ScreenRecorder?): FlickerBuilder = apply {
+        traceMonitors.removeIf { it is ScreenRecorder }
+        addMonitor(screenRecorder())
+    }
+
+    fun withoutScreenRecorder(): FlickerBuilder = apply {
+        traceMonitors.removeIf { it is ScreenRecorder }
+    }
+
+    /** Defines the setup commands executed before the [transitions] to test */
+    fun setup(commands: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
+        setupCommands.add(commands)
+    }
+
+    /** Defines the teardown commands executed after the [transitions] to test */
+    fun teardown(commands: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
+        teardownCommands.add(commands)
+    }
+
+    /** Defines the commands that trigger the behavior to test */
+    fun transitions(command: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
+        require(!usingExistingTraces) {
+            "Can't update transition after calling usingExistingTraces"
+        }
+        transitionCommands.add(command)
+    }
+
+    data class TraceFiles(
+        val wmTrace: File,
+        val layersTrace: File,
+        val transactions: File,
+        val transitions: File,
+        val eventLog: File
+    )
+
+    /** Use pre-executed results instead of running transitions to get the traces */
+    fun usingExistingTraces(_traceFiles: () -> TraceFiles): FlickerBuilder = apply {
+        val traceFiles = _traceFiles()
+        // Remove all trace monitor and use only monitor that read from existing trace file
+        this.traceMonitors.clear()
+        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.WM, traceFiles.wmTrace) })
+        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.SF, traceFiles.layersTrace) })
+        addMonitor(
+            NoTraceMonitor { it.addTraceResult(TraceType.TRANSACTION, traceFiles.transactions) }
+        )
+        addMonitor(
+            NoTraceMonitor { it.addTraceResult(TraceType.TRANSITION, traceFiles.transitions) }
+        )
+        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.EVENT_LOG, traceFiles.eventLog) })
+
+        // Remove all transitions execution
+        this.transitionCommands.clear()
+        this.usingExistingTraces = true
+    }
+
+    /** Creates a new Flicker runner based on the current builder configuration */
+    fun build(): IFlickerTestData {
+        return FlickerTestData(
+            instrumentation,
+            device,
+            outputDir,
+            traceMonitors,
+            setupCommands,
+            transitionCommands,
+            teardownCommands,
+            wmHelper
+        )
+    }
+
+    /** Returns a copy of the current builder with the changes of [block] applied */
+    fun copy(block: FlickerBuilder.() -> Unit) =
+        FlickerBuilder(
+                instrumentation,
+                outputDir.absoluteFile,
+                wmHelper,
+                setupCommands.toMutableList(),
+                transitionCommands.toMutableList(),
+                teardownCommands.toMutableList(),
+                device,
+                traceMonitors.toMutableList(),
+            )
+            .apply(block)
+
+    private fun addMonitor(newMonitor: ITransitionMonitor?) {
+        require(!usingExistingTraces) { "Can't add monitors after calling usingExistingTraces" }
+
+        if (newMonitor != null) {
+            traceMonitors.add(newMonitor)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTest.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTest.kt
new file mode 100644
index 0000000..4254d4f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTest.kt
@@ -0,0 +1,299 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.Scenario
+import android.tools.common.ScenarioBuilder
+import android.tools.common.Tag
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.region.RegionTraceSubject
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+import android.tools.common.io.IReader
+import android.tools.device.flicker.assertions.AssertionDataFactory
+import android.tools.device.flicker.assertions.AssertionStateDataFactory
+import android.tools.device.flicker.assertions.BaseAssertionRunner
+import android.tools.device.flicker.datastore.CachedAssertionRunner
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+
+/** Specification of a flicker test for JUnit ParameterizedRunner class */
+data class FlickerTest(
+    private val scenarioBuilder: ScenarioBuilder = ScenarioBuilder(),
+    private val resultReaderProvider: (IScenario) -> CachedResultReader = {
+        CachedResultReader(it, DEFAULT_TRACE_CONFIG)
+    },
+    private val subjectsParserProvider: (IReader) -> SubjectsParser = { SubjectsParser(it) },
+    private val runnerProvider: (Scenario) -> BaseAssertionRunner = {
+        val reader = resultReaderProvider(it)
+        val subjectsParser = subjectsParserProvider(reader)
+        CachedAssertionRunner(it, reader, subjectsParser)
+    }
+) {
+    private val wmAssertionFactory =
+        AssertionDataFactory(WindowManagerStateSubject::class, WindowManagerTraceSubject::class)
+    private val layersAssertionFactory =
+        AssertionDataFactory(LayerTraceEntrySubject::class, LayersTraceSubject::class)
+    private val eventLogAssertionFactory = AssertionStateDataFactory(EventLogSubject::class)
+
+    var scenario: Scenario = ScenarioBuilder().createEmptyScenario()
+        private set
+
+    override fun toString(): String = scenario.toString()
+
+    fun initialize(testClass: String): Scenario {
+        scenario = scenarioBuilder.forClass(testClass).build()
+        return scenario
+    }
+
+    fun <T> getConfigValue(key: String) = scenario.getConfigValue<T>(key)
+
+    /** Obtains a reader for the flicker result artifact */
+    val reader: IReader
+        get() = resultReaderProvider(scenario)
+
+    /**
+     * Execute [assertion] on the initial state of a WM trace (before transition)
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertWmStart(assertion: WindowManagerStateSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertWmStart") {
+            val assertionData =
+                wmAssertionFactory.createStartStateAssertion(assertion as FlickerSubject.() -> Unit)
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on the final state of a WM trace (after transition)
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertWmEnd(assertion: WindowManagerStateSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertWmEnd") {
+            val wrappedAssertion: (WindowManagerStateSubject) -> Unit = { assertion(it) }
+            val assertionData =
+                wmAssertionFactory.createEndStateAssertion(
+                    wrappedAssertion as (FlickerSubject) -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on a WM trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertWm(assertion: WindowManagerTraceSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertWm") {
+            val assertionData =
+                wmAssertionFactory.createTraceAssertion(
+                    assertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on a user defined moment ([tag]) of a WM trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertWmTag(tag: String, assertion: WindowManagerStateSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertWmTag") {
+            val assertionData =
+                wmAssertionFactory.createTagAssertion(tag, assertion as FlickerSubject.() -> Unit)
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on the visible region of WM state matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     * @param assertion Assertion predicate
+     */
+    fun assertWmVisibleRegion(
+        componentMatcher: IComponentMatcher,
+        assertion: RegionTraceSubject.() -> Unit
+    ) {
+        CrossPlatform.log.withTracing("assertWmVisibleRegion") {
+            val assertionData = buildWmVisibleRegionAssertion(componentMatcher, assertion)
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on the initial state of a SF trace (before transition)
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertLayersStart(assertion: LayerTraceEntrySubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertLayersStart") {
+            val assertionData =
+                layersAssertionFactory.createStartStateAssertion(
+                    assertion as FlickerSubject.() -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on the final state of a SF trace (after transition)
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertLayersEnd(assertion: LayerTraceEntrySubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertLayersEnd") {
+            val assertionData =
+                layersAssertionFactory.createEndStateAssertion(
+                    assertion as FlickerSubject.() -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on a SF trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertLayers(assertion: LayersTraceSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertLayers") {
+            val assertionData =
+                layersAssertionFactory.createTraceAssertion(
+                    assertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on a user defined moment ([tag]) of a SF trace
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertLayersTag(tag: String, assertion: LayerTraceEntrySubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertLayersTag") {
+            val assertionData =
+                layersAssertionFactory.createTagAssertion(
+                    tag,
+                    assertion as FlickerSubject.() -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on the visible region of a component on the layers trace matching
+     * [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
+     * Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise, calculates the
+     * visible region when the information is not available from the CE
+     * @param assertion Assertion predicate
+     */
+    @JvmOverloads
+    fun assertLayersVisibleRegion(
+        componentMatcher: IComponentMatcher,
+        useCompositionEngineRegionOnly: Boolean = true,
+        assertion: RegionTraceSubject.() -> Unit
+    ) {
+        CrossPlatform.log.withTracing("assertLayersVisibleRegion") {
+            val assertionData =
+                buildLayersVisibleRegionAssertion(
+                    componentMatcher,
+                    useCompositionEngineRegionOnly,
+                    assertion
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    /**
+     * Execute [assertion] on a sequence of event logs
+     *
+     * @param assertion Assertion predicate
+     */
+    fun assertEventLog(assertion: EventLogSubject.() -> Unit) {
+        CrossPlatform.log.withTracing("assertEventLog") {
+            val assertionData =
+                eventLogAssertionFactory.createTagAssertion(
+                    Tag.ALL,
+                    assertion as FlickerSubject.() -> Unit
+                )
+            doRunAssertion(assertionData)
+        }
+    }
+
+    private fun doRunAssertion(assertion: AssertionData) {
+        require(!scenario.isEmpty) { "Scenario shouldn't be empty" }
+        runnerProvider.invoke(scenario).runAssertion(assertion)?.let { throw it }
+    }
+
+    private fun buildWmVisibleRegionAssertion(
+        componentMatcher: IComponentMatcher,
+        assertion: RegionTraceSubject.() -> Unit
+    ): AssertionData {
+        val closedAssertion: WindowManagerTraceSubject.() -> Unit = {
+            require(!hasAssertions()) { "Subject was already used to execute assertions" }
+            // convert WindowManagerTraceSubject to RegionTraceSubject
+            val regionTraceSubject = visibleRegion(componentMatcher)
+            // add assertions to the regionTraceSubject's AssertionChecker
+            assertion(regionTraceSubject)
+            // loop through all entries to validate assertions
+            regionTraceSubject.forAllEntries()
+        }
+
+        return wmAssertionFactory.createTraceAssertion(
+            closedAssertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
+        )
+    }
+
+    private fun buildLayersVisibleRegionAssertion(
+        componentMatcher: IComponentMatcher,
+        useCompositionEngineRegionOnly: Boolean = true,
+        assertion: RegionTraceSubject.() -> Unit
+    ): AssertionData {
+        val closedAssertion: LayersTraceSubject.() -> Unit = {
+            require(!hasAssertions()) { "Subject was already used to execute assertions" }
+            // convert LayersTraceSubject to RegionTraceSubject
+            val regionTraceSubject = visibleRegion(componentMatcher, useCompositionEngineRegionOnly)
+
+            // add assertions to the regionTraceSubject's AssertionChecker
+            assertion(regionTraceSubject)
+            // loop through all entries to validate assertions
+            regionTraceSubject.forAllEntries()
+        }
+
+        return layersAssertionFactory.createTraceAssertion(
+            closedAssertion as (FlickerTraceSubject<*>) -> Unit
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestData.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestData.kt
new file mode 100644
index 0000000..a2e4f3a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestData.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.app.Instrumentation
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.uiautomator.UiDevice
+import java.io.File
+
+@DslMarker annotation class FlickerDslMarker
+
+/**
+ * Defines the runner for the flicker tests. This component is responsible for running the flicker
+ * tests and executing assertions on the traces to check for inconsistent behaviors on
+ * [WindowManagerTrace] and [LayersTrace]
+ */
+@FlickerDslMarker
+open class FlickerTestData(
+    /** Instrumentation to run the tests */
+    override val instrumentation: Instrumentation,
+    /** Test automation component used to interact with the device */
+    override val device: UiDevice,
+    /** Output directory for test results */
+    override val outputDir: File,
+    /** Enabled tracing monitors */
+    override val traceMonitors: List<ITransitionMonitor>,
+    /** Commands to be executed before the transition */
+    override val transitionSetup: List<IFlickerTestData.() -> Any>,
+    /** Test commands */
+    override val transitions: List<IFlickerTestData.() -> Any>,
+    /** Commands to be executed after the transition */
+    override val transitionTeardown: List<IFlickerTestData.() -> Any>,
+    /** Helper object for WM Synchronization */
+    override val wmHelper: WindowManagerStateHelper
+) : AbstractFlickerTestData()
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestFactory.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestFactory.kt
new file mode 100644
index 0000000..c071467
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/FlickerTestFactory.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.ScenarioBuilder
+
+/**
+ * Factory for creating JUnit4 compatible tests based on the flicker DSL
+ *
+ * This class recreates behavior from JUnit5 TestFactory that is not available on JUnit4
+ */
+object FlickerTestFactory {
+    /**
+     * Gets a list of test configurations.
+     *
+     * Each configuration has only a start orientation.
+     */
+    @JvmOverloads
+    @JvmStatic
+    fun nonRotationTests(
+        supportedRotations: List<Rotation> = listOf(Rotation.ROTATION_0, Rotation.ROTATION_90),
+        supportedNavigationModes: List<NavBar> = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL),
+        extraArgs: Map<String, Any> = emptyMap()
+    ): List<FlickerTest> {
+        return supportedNavigationModes.flatMap { navBarMode ->
+            supportedRotations.map { rotation ->
+                createFlickerTest(navBarMode, rotation, rotation, extraArgs)
+            }
+        }
+    }
+
+    /**
+     * Gets a list of test configurations.
+     *
+     * Each configuration has a start and end orientation.
+     */
+    @JvmOverloads
+    @JvmStatic
+    fun rotationTests(
+        supportedRotations: List<Rotation> = listOf(Rotation.ROTATION_0, Rotation.ROTATION_90),
+        supportedNavigationModes: List<NavBar> = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL),
+        extraArgs: Map<String, Any> = emptyMap()
+    ): List<FlickerTest> {
+        return supportedNavigationModes.flatMap { navBarMode ->
+            supportedRotations
+                .flatMap { start -> supportedRotations.map { end -> start to end } }
+                .filter { (start, end) -> start != end }
+                .map { (start, end) -> createFlickerTest(navBarMode, start, end, extraArgs) }
+        }
+    }
+
+    private fun createFlickerTest(
+        navBarMode: NavBar,
+        startRotation: Rotation,
+        endRotation: Rotation,
+        extraArgs: Map<String, Any>
+    ) =
+        FlickerTest(
+            ScenarioBuilder()
+                .withStartRotation(startRotation)
+                .withEndRotation(endRotation)
+                .withNavBarMode(navBarMode)
+                .withExtraConfigs(extraArgs)
+        )
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/IFlickerTestData.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/IFlickerTestData.kt
new file mode 100644
index 0000000..3f1b77a
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/IFlickerTestData.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.app.Instrumentation
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.uiautomator.UiDevice
+import java.io.File
+
+interface IFlickerTestData {
+    /** Instrumentation to run the tests */
+    val instrumentation: Instrumentation
+    /** Test automation component used to interact with the device */
+    val device: UiDevice
+    /** Output directory for test results */
+    val outputDir: File
+    /** Enabled tracing monitors */
+    val traceMonitors: List<ITransitionMonitor>
+    /** Commands to be executed before the transition */
+    val transitionSetup: List<IFlickerTestData.() -> Any>
+    /** Test commands */
+    val transitions: List<IFlickerTestData.() -> Any>
+    /** Commands to be executed after the transition */
+    val transitionTeardown: List<IFlickerTestData.() -> Any>
+    /** Helper object for WM Synchronization */
+    val wmHelper: WindowManagerStateHelper
+
+    fun setAssertionsCheckedCallback(callback: (Boolean) -> Unit)
+
+    fun setCreateTagListener(callback: (String) -> Unit)
+
+    fun clearTagListener()
+
+    /**
+     * Runs a set of commands and, at the end, creates a tag containing the device state
+     *
+     * @param tag Identifier for the tag to be created
+     * @param commands Commands to execute before creating the tag
+     * @throws IllegalArgumentException If [tag] cannot be converted to a valid filename
+     */
+    fun withTag(tag: String, commands: IFlickerTestData.() -> Any)
+
+    fun createTag(tag: String)
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Consts.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Consts.kt
new file mode 100644
index 0000000..7c43131
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Consts.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.tools.common.FLICKER_TAG
+
+internal const val FLICKER_RUNNER_TAG = "$FLICKER_TAG-Runner"
+const val EMPTY_TRANSITIONS_ERROR = "A flicker test must include transitions to run"
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/ExecutionError.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/ExecutionError.kt
new file mode 100644
index 0000000..c9ed0ce
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/ExecutionError.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+/**
+ * Base class for flicker execution failures
+ *
+ * @param inner cause
+ */
+open class ExecutionError(private val inner: Throwable) : Throwable(inner) {
+    init {
+        super.setStackTrace(inner.stackTrace)
+    }
+
+    override val message = inner.toString()
+
+    override val cause = inner.cause ?: super.cause
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/SetupTeardownRule.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/SetupTeardownRule.kt
new file mode 100644
index 0000000..019ff47
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/SetupTeardownRule.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.platform.test.rule.ArtifactSaver
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Test rule to run transition setup and teardown
+ *
+ * @param flicker test definition
+ * @param resultWriter to write
+ * @param scenario to run the transition
+ * @param instrumentation to interact with the device
+ * @param setupCommands to run before the transition
+ * @param teardownCommands to run after the transition
+ * @param wmHelper to stabilize the UI before/after transitions
+ */
+class SetupTeardownRule(
+    private val flicker: IFlickerTestData,
+    private val resultWriter: ResultWriter,
+    private val scenario: IScenario,
+    private val instrumentation: Instrumentation,
+    private val setupCommands: List<IFlickerTestData.() -> Any> = flicker.transitionSetup,
+    private val teardownCommands: List<IFlickerTestData.() -> Any> = flicker.transitionTeardown,
+    private val wmHelper: WindowManagerStateHelper = flicker.wmHelper
+) : TestRule {
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                try {
+                    doRunTransitionSetup(description)
+                    base?.evaluate()
+                } finally {
+                    doRunTransitionTeardown(description)
+                }
+            }
+        }
+    }
+
+    @Throws(TransitionSetupFailure::class)
+    private fun doRunTransitionSetup(description: Description?) {
+        CrossPlatform.log.withTracing("doRunTransitionSetup") {
+            Utils.notifyRunnerProgress(scenario, "Running transition setup for $description")
+            try {
+                setupCommands.forEach { it.invoke(flicker) }
+                Utils.doWaitForUiStabilize(wmHelper)
+            } catch (e: Throwable) {
+                ArtifactSaver.onError(Utils.expandDescription(description, "setup"), e)
+                throw TransitionSetupFailure(e)
+            }
+        }
+    }
+
+    @Throws(TransitionTeardownFailure::class)
+    private fun doRunTransitionTeardown(description: Description?) {
+        CrossPlatform.log.withTracing("doRunTransitionTeardown") {
+            Utils.notifyRunnerProgress(scenario, "Running transition teardown for $description")
+            try {
+                teardownCommands.forEach { it.invoke(flicker) }
+                Utils.doWaitForUiStabilize(wmHelper)
+            } catch (e: Throwable) {
+                ArtifactSaver.onError(Utils.expandDescription(description, "teardown"), e)
+                throw TransitionTeardownFailure(e)
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TraceMonitorRule.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TraceMonitorRule.kt
new file mode 100644
index 0000000..66ea78e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TraceMonitorRule.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.platform.test.rule.ArtifactSaver
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.IScenario
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Test rule to start and stop trace monitors and update [resultWriter]
+ *
+ * @param traceMonitors to collect device data
+ * @param scenario to run the transition
+ * @param wmHelper to stabilize the UI before/after transitions
+ * @param resultWriter to write
+ * @param instrumentation to interact with the device
+ */
+class TraceMonitorRule(
+    private val traceMonitors: List<ITransitionMonitor>,
+    private val scenario: IScenario,
+    private val wmHelper: WindowManagerStateHelper,
+    private val resultWriter: ResultWriter,
+    private val instrumentation: Instrumentation
+) : TestRule {
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                try {
+                    doStartMonitors(description)
+                    base?.evaluate()
+                } finally {
+                    doStopMonitors(description)
+                }
+            }
+        }
+    }
+
+    private fun doStartMonitors(description: Description?) {
+        CrossPlatform.log.withTracing("doStartMonitors") {
+            Utils.notifyRunnerProgress(scenario, "Starting traces for $description")
+            traceMonitors.forEach {
+                try {
+                    it.start()
+                } catch (e: Throwable) {
+                    ArtifactSaver.onError(Utils.expandDescription(description, "startTrace"), e)
+                    throw TransitionTracingFailure(e)
+                }
+            }
+        }
+    }
+
+    private fun doStopMonitors(description: Description?) {
+        CrossPlatform.log.withTracing("doStopMonitors") {
+            Utils.notifyRunnerProgress(scenario, "Stopping traces for $description")
+            val errors =
+                traceMonitors.map {
+                    runCatching {
+                        try {
+                            it.stop(resultWriter)
+                        } catch (e: Throwable) {
+                            ArtifactSaver.onError(
+                                Utils.expandDescription(description, "stopTrace"),
+                                e
+                            )
+                            CrossPlatform.log.e(FLICKER_TAG, "Unable to stop $it", e)
+                            throw TransitionTracingFailure(e)
+                        }
+                    }
+                }
+            errors.firstOrNull { it.isFailure }?.exceptionOrNull()?.let { throw it }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionFailure.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionFailure.kt
new file mode 100644
index 0000000..4b0b1dd
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionFailure.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+/**
+ * Exception type for errors occurred during the transition
+ *
+ * @param inner cause
+ */
+class TransitionExecutionFailure(inner: Throwable) : ExecutionError(inner) {
+    override val message = "Transition execution failed: ${super.message}"
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRule.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRule.kt
new file mode 100644
index 0000000..1f2ce18
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRule.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.platform.test.rule.ArtifactSaver
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.io.TraceType
+import android.tools.device.flicker.FlickerTag
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.getCurrentState
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.now
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.util.EventLog
+import java.io.File
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Test rule to execute the transition and update [resultWriter]
+ *
+ * @param flicker test definition
+ * @param resultWriter to write
+ * @param scenario to run the transition
+ * @param instrumentation to interact with the device
+ * @param commands to run during the transition
+ * @param wmHelper to stabilize the UI before/after transitions
+ */
+class TransitionExecutionRule(
+    private val flicker: IFlickerTestData,
+    private val resultWriter: ResultWriter,
+    private val scenario: IScenario,
+    private val instrumentation: Instrumentation = flicker.instrumentation,
+    private val commands: List<IFlickerTestData.() -> Any> = flicker.transitions,
+    private val wmHelper: WindowManagerStateHelper = flicker.wmHelper
+) : TestRule {
+    private var tags = mutableSetOf<String>()
+
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                CrossPlatform.log.withTracing("transition") {
+                    try {
+                        Utils.notifyRunnerProgress(scenario, "Running transition $description")
+                        doRunBeforeTransition()
+                        commands.forEach { it.invoke(flicker) }
+                        base?.evaluate()
+                    } catch (e: Throwable) {
+                        ArtifactSaver.onError(Utils.expandDescription(description, "transition"), e)
+                        throw if (e is AssertionError) {
+                            e
+                        } else {
+                            TransitionExecutionFailure(e)
+                        }
+                    } finally {
+                        doRunAfterTransition()
+                    }
+                }
+            }
+        }
+    }
+
+    private fun doRunBeforeTransition() {
+        CrossPlatform.log.withTracing("doRunBeforeTransition") {
+            Utils.notifyRunnerProgress(scenario, "Running doRunBeforeTransition")
+            CrossPlatform.log.d(FLICKER_RUNNER_TAG, "doRunBeforeTransition")
+            val now = now()
+            resultWriter.setTransitionStartTime(now)
+            EventLog.writeEvent(
+                FlickerTag.TRANSITION_START,
+                now.unixNanos,
+                now.elapsedNanos,
+                now.systemUptimeNanos
+            )
+            flicker.setCreateTagListener { doCreateTag(it) }
+            doValidate()
+        }
+    }
+
+    private fun doRunAfterTransition() {
+        CrossPlatform.log.withTracing("doRunAfterTransition") {
+            Utils.notifyRunnerProgress(scenario, "Running doRunAfterTransition")
+            CrossPlatform.log.d(FLICKER_RUNNER_TAG, "doRunAfterTransition")
+            Utils.doWaitForUiStabilize(wmHelper)
+            val now = now()
+            resultWriter.setTransitionEndTime(now)
+            EventLog.writeEvent(
+                FlickerTag.TRANSITION_END,
+                now.unixNanos,
+                now.elapsedNanos,
+                now.systemUptimeNanos
+            )
+            flicker.clearTagListener()
+        }
+    }
+
+    private fun doValidate() {
+        require(commands.isNotEmpty()) { EMPTY_TRANSITIONS_ERROR }
+    }
+
+    private fun doValidateTag(tag: String) {
+        require(!tags.contains(tag)) { "Tag `$tag` already used" }
+        require(!tag.contains(" ")) { "Tag can't contain spaces, instead it was `$tag`" }
+        require(!tag.contains("__")) { "Tag can't `__``, instead it was `$tag`" }
+    }
+
+    private fun doCreateTag(tag: String) {
+        CrossPlatform.log.withTracing("doRunAfterTransition") {
+            Utils.notifyRunnerProgress(scenario, "Creating tag $tag")
+            doValidateTag(tag)
+            tags.add(tag)
+
+            val deviceStateBytes = getCurrentState()
+            val wmDumpFile = File.createTempFile(TraceType.WM_DUMP.fileName, tag)
+            val layersDumpFile = File.createTempFile(TraceType.SF_DUMP.fileName, tag)
+
+            wmDumpFile.writeBytes(deviceStateBytes.first)
+            layersDumpFile.writeBytes(deviceStateBytes.second)
+
+            resultWriter.addTraceResult(TraceType.WM_DUMP, wmDumpFile, tag)
+            resultWriter.addTraceResult(TraceType.SF_DUMP, layersDumpFile, tag)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionRunner.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionRunner.kt
new file mode 100644
index 0000000..dec79ad
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionRunner.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.datastore.CachedResultWriter
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.io.ResultWriter
+import org.junit.rules.RuleChain
+import org.junit.runner.Description
+
+/**
+ * Transition runner that executes a default device setup (based on [scenario]) as well as the
+ * flicker setup/transition/teardown
+ */
+class TransitionRunner(
+    private val scenario: IScenario,
+    private val instrumentation: Instrumentation,
+    private val resultWriter: ResultWriter = CachedResultWriter()
+) {
+    /** Executes [flicker] transition and returns the result */
+    fun execute(flicker: IFlickerTestData, description: Description?): IResultData {
+        return CrossPlatform.log.withTracing("TransitionRunner:execute") {
+            resultWriter.forScenario(scenario).withOutputDir(flicker.outputDir)
+
+            val ruleChain = buildTestRuleChain(flicker)
+            try {
+                ruleChain.apply(/* statement */ null, description).evaluate()
+                resultWriter.setRunComplete()
+            } catch (e: Throwable) {
+                resultWriter.setRunFailed(e)
+            }
+            resultWriter.write()
+        }
+    }
+
+    /**
+     * Create the default flicker test setup rules. In order:
+     * - unlock device
+     * - change orientation
+     * - change navigation mode
+     * - launch an app
+     * - remove all apps
+     * - go home
+     *
+     * (b/186740751) An app should be launched because, after changing the navigation mode, the
+     * first app launch is handled as a screen size change (similar to a rotation), this causes
+     * different problems during testing (e.g. IME now shown on app launch)
+     */
+    private fun buildTestRuleChain(flicker: IFlickerTestData): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(
+                    scenario.navBarMode.value,
+                    /* changeNavigationModeAfterTest */ false
+                )
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    scenario.startRotation,
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+            .around(
+                TraceMonitorRule(
+                    flicker.traceMonitors,
+                    scenario,
+                    flicker.wmHelper,
+                    resultWriter,
+                    instrumentation
+                )
+            )
+            .around(SetupTeardownRule(flicker, resultWriter, scenario, instrumentation))
+            .around(TransitionExecutionRule(flicker, resultWriter, scenario, instrumentation))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionSetupFailure.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionSetupFailure.kt
new file mode 100644
index 0000000..d38d939
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionSetupFailure.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+/**
+ * Exception type for errors occurred during the transition setup
+ *
+ * @param inner cause
+ */
+class TransitionSetupFailure(inner: Throwable) : ExecutionError(inner) {
+    override val message = "Setup failed: ${super.message}"
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTeardownFailure.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTeardownFailure.kt
new file mode 100644
index 0000000..53fb3a8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTeardownFailure.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+/**
+ * Exception type for errors occurred during the transition teardown
+ *
+ * @param inner cause
+ */
+class TransitionTeardownFailure(inner: Throwable) : ExecutionError(inner) {
+    override val message = "Teardown failed: ${super.message}"
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTracingFailure.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTracingFailure.kt
new file mode 100644
index 0000000..5176e40
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/TransitionTracingFailure.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+/**
+ * Exception type for errors occurred during tracing
+ *
+ * @param inner cause
+ */
+class TransitionTracingFailure(inner: Throwable) : ExecutionError(inner) {
+    override val message = "Tracing failed: ${super.message}"
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Utils.kt b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Utils.kt
new file mode 100644
index 0000000..45cc3a9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/legacy/runner/Utils.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.traces.ConditionList
+import android.tools.common.traces.ConditionsFactory
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.runner.Description
+
+/** Helper class for flicker transition rules */
+object Utils {
+    /**
+     * Conditions that determine when the UI is in a stable and no windows or layers are animating
+     * or changing state.
+     */
+    private val UI_STABLE_CONDITIONS =
+        ConditionList(
+            listOf(
+                ConditionsFactory.isWMStateComplete(),
+                ConditionsFactory.hasLayersAnimating().negate()
+            )
+        )
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    internal fun doWaitForUiStabilize(wmHelper: WindowManagerStateHelper) {
+        wmHelper.StateSyncBuilder().add(UI_STABLE_CONDITIONS).waitFor()
+    }
+
+    internal fun notifyRunnerProgress(scenario: IScenario, msg: String) {
+        CrossPlatform.log.d(FLICKER_RUNNER_TAG, "${scenario.key} - $msg")
+        val results = Bundle()
+        results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "$msg\n")
+        instrumentation.sendStatus(1, results)
+    }
+
+    internal fun expandDescription(description: Description?, suffix: String): Description? =
+        Description.createTestDescription(
+            description?.className,
+            "${description?.displayName}-$suffix",
+            description?.annotations?.toTypedArray()
+        )
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/rules/ChangeDisplayOrientationRule.kt b/libraries/flicker/src/android/tools/device/flicker/rules/ChangeDisplayOrientationRule.kt
new file mode 100644
index 0000000..27b2e14
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/rules/ChangeDisplayOrientationRule.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.device.flicker.rules
+
+import android.app.Instrumentation
+import android.content.Context
+import android.os.RemoteException
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Changes display orientation before a test
+ *
+ * @param targetOrientation Target orientation
+ * @param instrumentation Instrumentation mechanism to use
+ * @param clearCacheAfterParsing If the caching used while parsing the proto should be
+ * ```
+ *                               cleared or remain in memory
+ * ```
+ */
+data class ChangeDisplayOrientationRule
+@JvmOverloads
+constructor(
+    private val targetOrientation: Rotation,
+    private val resetOrientationAfterTest: Boolean = true,
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    private val clearCacheAfterParsing: Boolean = true
+) : TestWatcher() {
+    private var initialOrientation = Rotation.ROTATION_0
+
+    override fun starting(description: Description?) {
+        CrossPlatform.log.withTracing("ChangeDisplayOrientationRule:starting") {
+            CrossPlatform.log.v(
+                FLICKER_TAG,
+                "Changing display orientation to " +
+                    "$targetOrientation ${targetOrientation.description}"
+            )
+            val wm =
+                instrumentation.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+            initialOrientation = Rotation.getByValue(wm.defaultDisplay.rotation)
+            setRotation(targetOrientation, instrumentation, clearCacheAfterParsing)
+        }
+    }
+
+    override fun finished(description: Description?) {
+        CrossPlatform.log.withTracing("ChangeDisplayOrientationRule:finished") {
+            if (resetOrientationAfterTest) {
+                setRotation(initialOrientation, instrumentation, clearCacheAfterParsing)
+            }
+        }
+    }
+
+    companion object {
+        @JvmOverloads
+        fun setRotation(
+            rotation: Rotation,
+            instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+            clearCacheAfterParsing: Boolean = true,
+            wmHelper: WindowManagerStateHelper =
+                WindowManagerStateHelper(
+                    instrumentation,
+                    clearCacheAfterParsing = clearCacheAfterParsing
+                )
+        ) {
+            val device: UiDevice = UiDevice.getInstance(instrumentation)
+
+            try {
+                when (rotation) {
+                    Rotation.ROTATION_270 -> device.setOrientationRight()
+                    Rotation.ROTATION_90 -> device.setOrientationLeft()
+                    Rotation.ROTATION_0 -> device.setOrientationNatural()
+                    else -> device.setOrientationNatural()
+                }
+
+                if (wmHelper.currentState.wmState.canRotate) {
+                    wmHelper.StateSyncBuilder().withRotation(rotation).waitForAndVerify()
+                } else {
+                    wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+                    CrossPlatform.log.v(FLICKER_TAG, "Rotation is not allowed in the state")
+                    return
+                }
+
+                // During seamless rotation the app window is shown
+                val currWmState = wmHelper.currentState.wmState
+                if (currWmState.visibleWindows.none { it.isFullscreen }) {
+                    wmHelper
+                        .StateSyncBuilder()
+                        .withNavOrTaskBarVisible()
+                        .withStatusBarVisible()
+                        .waitForAndVerify()
+                }
+            } catch (e: RemoteException) {
+                throw RuntimeException(e)
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/rules/FlickerServiceRule.kt b/libraries/flicker/src/android/tools/device/flicker/rules/FlickerServiceRule.kt
new file mode 100644
index 0000000..cd8e18f
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/rules/FlickerServiceRule.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.device.flicker.rules
+
+import android.platform.test.rule.TestWatcher
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.TimestampFactory
+import android.tools.device.flicker.FlickerServiceResultsCollector
+import android.tools.device.flicker.FlickerServiceTracesCollector
+import android.tools.device.flicker.IFlickerServiceResultsCollector
+import android.tools.device.traces.ANDROID_LOGGER
+import android.tools.device.traces.formatRealTimestamp
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.AssumptionViolatedException
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+
+/**
+ * A test rule that runs Flicker as a Service on the tests this rule is applied to.
+ *
+ * Note there are performance implications to using this test rule in tests. Tracing will be enabled
+ * during the test which will slow down everything. So if the test is performance critical then an
+ * alternative should be used.
+ *
+ * @see TODO for examples on how to use this test rule in your own tests
+ */
+open class FlickerServiceRule
+@JvmOverloads
+constructor(
+    private val metricsCollector: IFlickerServiceResultsCollector =
+        FlickerServiceResultsCollector(
+            tracesCollector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir()),
+            instrumentation = InstrumentationRegistry.getInstrumentation()
+        ),
+    // defaults to true
+    private val failTestOnFaasFailure: Boolean =
+        InstrumentationRegistry.getArguments().getString("faas:blocking")?.let { it.toBoolean() }
+            ?: true
+) : TestWatcher() {
+
+    init {
+        CrossPlatform.setLogger(ANDROID_LOGGER)
+            .setTimestampFactory(TimestampFactory { formatRealTimestamp(it) })
+    }
+
+    /** Invoked when a test is about to start */
+    public override fun starting(description: Description) {
+        CrossPlatform.log.i(LOG_TAG, "Test starting $description")
+        metricsCollector.testStarted(description)
+    }
+
+    /** Invoked when a test succeeds */
+    public override fun succeeded(description: Description) {
+        CrossPlatform.log.i(LOG_TAG, "Test succeeded $description")
+    }
+
+    /** Invoked when a test fails */
+    public override fun failed(e: Throwable?, description: Description) {
+        CrossPlatform.log.e(LOG_TAG, "$description test failed  with $e")
+        metricsCollector.testFailure(Failure(description, e))
+    }
+
+    /** Invoked when a test is skipped due to a failed assumption. */
+    public override fun skipped(e: AssumptionViolatedException, description: Description) {
+        CrossPlatform.log.i(LOG_TAG, "Test skipped $description with $e")
+        metricsCollector.testSkipped(description)
+    }
+
+    /** Invoked when a test method finishes (whether passing or failing) */
+    public override fun finished(description: Description) {
+        CrossPlatform.log.i(LOG_TAG, "Test finished $description")
+        metricsCollector.testFinished(description)
+        if (metricsCollector.executionErrors.isNotEmpty()) {
+            for (executionError in metricsCollector.executionErrors) {
+                CrossPlatform.log.e(LOG_TAG, "FaaS reported execution errors", executionError)
+            }
+            throw metricsCollector.executionErrors[0]
+        }
+        if (failTestOnFaasFailure && metricsCollector.testContainsFlicker(description)) {
+            throw metricsCollector.resultsForTest(description).first { it.failed }.assertionError
+                ?: error("Unexpectedly missing assertion error")
+        }
+    }
+
+    companion object {
+        const val LOG_TAG = "$FLICKER_TAG-ServiceRule"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/rules/LaunchAppRule.kt b/libraries/flicker/src/android/tools/device/flicker/rules/LaunchAppRule.kt
new file mode 100644
index 0000000..cd6a5de
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/rules/LaunchAppRule.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.device.flicker.rules
+
+import android.app.Instrumentation
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Launches an app before the test
+ *
+ * @param instrumentation Instrumentation mechanism to use
+ * @param wmHelper WM/SF synchronization helper
+ * @param appHelper App to launch
+ * @param clearCacheAfterParsing If the caching used while parsing the proto should be
+ * ```
+ *                               cleared or remain in memory
+ * ```
+ */
+class LaunchAppRule
+@JvmOverloads
+constructor(
+    private val appHelper: StandardAppHelper,
+    private val instrumentation: Instrumentation = appHelper.mInstrumentation,
+    private val clearCacheAfterParsing: Boolean = true,
+    private val wmHelper: WindowManagerStateHelper =
+        WindowManagerStateHelper(clearCacheAfterParsing = clearCacheAfterParsing)
+) : TestWatcher() {
+    @JvmOverloads
+    constructor(
+        componentMatcher: ComponentNameMatcher,
+        appName: String = "",
+        instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+        clearCache: Boolean = true,
+        wmHelper: WindowManagerStateHelper =
+            WindowManagerStateHelper(clearCacheAfterParsing = clearCache)
+    ) : this(
+        StandardAppHelper(instrumentation, appName, componentMatcher),
+        instrumentation,
+        clearCache,
+        wmHelper
+    )
+
+    override fun starting(description: Description?) {
+        CrossPlatform.log.withTracing("LaunchAppRule:finished") {
+            CrossPlatform.log.v(FLICKER_TAG, "Launching app $appHelper")
+            appHelper.launchViaIntent()
+            appHelper.exit(wmHelper)
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/flicker/rules/RemoveAllTasksButHomeRule.kt b/libraries/flicker/src/android/tools/device/flicker/rules/RemoveAllTasksButHomeRule.kt
new file mode 100644
index 0000000..487de0e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/flicker/rules/RemoveAllTasksButHomeRule.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.device.flicker.rules
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/** Test rule to ensure no tasks as running before executing the test */
+class RemoveAllTasksButHomeRule() : TestWatcher() {
+    override fun starting(description: Description?) {
+        CrossPlatform.log.withTracing("RemoveAllTasksButHomeRule:finished") {
+            CrossPlatform.log.v(FLICKER_TAG, "Removing all tasks (except home)")
+            removeAllTasksButHome()
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun removeAllTasksButHome() {
+            val atm = ActivityTaskManager.getService()
+            atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
+        }
+
+        private val ALL_ACTIVITY_TYPE_BUT_HOME =
+            intArrayOf(
+                WindowConfiguration.ACTIVITY_TYPE_STANDARD,
+                WindowConfiguration.ACTIVITY_TYPE_ASSISTANT,
+                WindowConfiguration.ACTIVITY_TYPE_RECENTS,
+                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+            )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/helpers/AutomationUtils.kt b/libraries/flicker/src/android/tools/device/helpers/AutomationUtils.kt
new file mode 100644
index 0000000..ae82b81
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/helpers/AutomationUtils.kt
@@ -0,0 +1,371 @@
+/*
+ * 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.device.helpers
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.RemoteException
+import android.os.SystemClock
+import android.tools.common.CrossPlatform
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.traces.ConditionsFactory
+import android.tools.device.helpers.WindowUtils.displayBounds
+import android.tools.device.helpers.WindowUtils.estimateNavigationBarPosition
+import android.tools.device.traces.executeShellCommand
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toAndroidRect
+import android.util.Rational
+import android.view.View
+import android.view.ViewConfiguration
+import androidx.annotation.VisibleForTesting
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.Configurator
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.Assert
+import org.junit.Assert.assertNotNull
+
+const val FIND_TIMEOUT: Long = 10000
+const val FAST_WAIT_TIMEOUT: Long = 0
+val DOCKED_STACK_DIVIDER = ComponentNameMatcher("", "DockedStackDivider")
+const val IME_PACKAGE = "com.google.android.inputmethod.latin"
+@VisibleForTesting const val SYSTEMUI_PACKAGE = "com.android.systemui"
+private val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L
+private const val TAG = "FLICKER"
+
+/**
+ * Sets [android.app.UiAutomation.waitForIdle] global timeout to 0 causing the
+ * [android.app.UiAutomation.waitForIdle] function to timeout instantly. This removes some delays
+ * when using the UIAutomator library required to create fast UI transitions.
+ */
+fun setFastWait() {
+    Configurator.getInstance().waitForIdleTimeout = FAST_WAIT_TIMEOUT
+}
+
+/** Reverts [android.app.UiAutomation.waitForIdle] to default behavior. */
+fun setDefaultWait() {
+    Configurator.getInstance().waitForIdleTimeout = FIND_TIMEOUT
+}
+
+/** Checks if the device is running on gestural or 2-button navigation modes */
+fun UiDevice.isQuickstepEnabled(): Boolean {
+    val enabled = this.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null
+    CrossPlatform.log.d(TAG, "Quickstep enabled: $enabled")
+    return enabled
+}
+
+/** Checks if the display is rotated or not */
+fun UiDevice.isRotated(): Boolean {
+    return Rotation.getByValue(this.displayRotation).isRotated()
+}
+
+/** Reopens the first device window from the list of recent apps (overview) */
+fun UiDevice.reopenAppFromOverview(wmHelper: WindowManagerStateHelper) {
+    val x = this.displayWidth / 2
+    val y = this.displayHeight / 2
+    this.click(x, y)
+
+    wmHelper.StateSyncBuilder().withAppTransitionIdle().waitFor()
+}
+
+/**
+ * Shows quickstep
+ *
+ * @throws AssertionError When quickstep does not appear
+ */
+fun UiDevice.openQuickstep(wmHelper: WindowManagerStateHelper) {
+    if (this.isQuickstepEnabled()) {
+        val navBar = this.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"))
+
+        // TODO(vishnun) investigate why this object cannot be found.
+        val navBarVisibleBounds: Rect =
+            if (navBar != null) {
+                navBar.visibleBounds
+            } else {
+                CrossPlatform.log.e(TAG, "Could not find nav bar, infer location")
+                estimateNavigationBarPosition(Rotation.ROTATION_0).bounds.toAndroidRect()
+            }
+
+        val startX = navBarVisibleBounds.centerX()
+        val startY = navBarVisibleBounds.centerY()
+        val endX: Int
+        val endY: Int
+        val height: Int
+        val steps: Int
+        if (this.isRotated()) {
+            height = this.displayWidth
+            endX = height * 2 / 3
+            endY = navBarVisibleBounds.centerY()
+            steps = (endX - startX) / 100 // 100 px/step
+        } else {
+            height = this.displayHeight
+            endX = navBarVisibleBounds.centerX()
+            endY = height * 2 / 3
+            steps = (startY - endY) / 100 // 100 px/step
+        }
+        // Swipe from nav bar to 2/3rd down the screen.
+        this.swipe(startX, startY, endX, endY, steps)
+    }
+
+    // use a long timeout to wait until recents populated
+    val recentsSysUISelector = By.res(this.launcherPackageName, "overview_panel")
+    var recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
+
+    // Quickstep detection is flaky on AOSP, UIDevice doesn't always find SysUI elements
+    // If it couldn't find, try pressing 'recent items' button
+    if (recents == null) {
+        try {
+            this.pressRecentApps()
+        } catch (e: RemoteException) {
+            throw RuntimeException(e)
+        }
+        recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
+    }
+    assertNotNull("Recent items didn't appear", recents)
+    wmHelper
+        .StateSyncBuilder()
+        .withNavOrTaskBarVisible()
+        .withStatusBarVisible()
+        .withAppTransitionIdle()
+        .waitForAndVerify()
+}
+
+private fun getLauncherOverviewSelector(device: UiDevice): BySelector {
+    return By.res(device.launcherPackageName, "overview_panel")
+}
+
+private fun longPressRecents(device: UiDevice) {
+    val recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps")
+    val recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find 'recent items' button", recentsButton)
+    recentsButton.click(LONG_PRESS_TIMEOUT)
+}
+
+/** Wait for any IME view to appear */
+fun UiDevice.waitForIME(): Boolean {
+    val ime = this.wait(Until.findObject(By.pkg(IME_PACKAGE)), FIND_TIMEOUT)
+    return ime != null
+}
+
+private fun openQuickStepAndLongPressOverviewIcon(
+    device: UiDevice,
+    wmHelper: WindowManagerStateHelper
+) {
+    if (device.isQuickstepEnabled()) {
+        device.openQuickstep(wmHelper)
+    } else {
+        try {
+            device.pressRecentApps()
+        } catch (e: RemoteException) {
+            CrossPlatform.log.e(TAG, "launchSplitScreen", e)
+        }
+    }
+    val overviewIconSelector = By.res(device.launcherPackageName, "icon").clazz(View::class.java)
+    val overviewIcon = device.wait(Until.findObject(overviewIconSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find app icon in Overview", overviewIcon)
+    overviewIcon.click()
+}
+
+fun UiDevice.openQuickStepAndClearRecentAppsFromOverview(wmHelper: WindowManagerStateHelper) {
+    if (this.isQuickstepEnabled()) {
+        this.openQuickstep(wmHelper)
+    } else {
+        try {
+            this.pressRecentApps()
+        } catch (e: RemoteException) {
+            CrossPlatform.log.e(TAG, "launchSplitScreen", e)
+        }
+    }
+    for (i in 0..9) {
+        this.swipe(
+            this.displayWidth / 2,
+            this.displayHeight / 2,
+            this.displayWidth,
+            this.displayHeight / 2,
+            5
+        )
+        // If "Clear all"  button appears, use it
+        val clearAllSelector = By.res(this.launcherPackageName, "clear_all")
+        wait(Until.findObject(clearAllSelector), FAST_WAIT_TIMEOUT)?.click()
+    }
+    this.pressHome()
+}
+
+/**
+ * Opens quick step and puts the first app from the list of recently used apps into split-screen
+ *
+ * @throws AssertionError when unable to open the list of recently used apps, or when it does not
+ * contain a button to enter split screen mode
+ */
+fun UiDevice.launchSplitScreen(wmHelper: WindowManagerStateHelper) {
+    openQuickStepAndLongPressOverviewIcon(this, wmHelper)
+    val splitScreenButtonSelector = By.text("Split screen")
+    val splitScreenButton = this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find Split screen button in Overview", splitScreenButton)
+    splitScreenButton.click()
+
+    // Wait for animation to complete.
+    this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
+    wmHelper
+        .StateSyncBuilder()
+        .add(ConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER))
+        .withAppTransitionIdle()
+        .waitForAndVerify()
+
+    if (!this.isInSplitScreen()) {
+        Assert.fail("Unable to find Split screen divider")
+    }
+}
+
+/** Checks if the recent application is able to split screen(resizeable) */
+fun UiDevice.canSplitScreen(wmHelper: WindowManagerStateHelper): Boolean {
+    openQuickStepAndLongPressOverviewIcon(this, wmHelper)
+    val splitScreenButtonSelector = By.text("Split screen")
+    val canSplitScreen =
+        this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT) != null
+    this.pressHome()
+    return canSplitScreen
+}
+
+/** Checks if the device is in split screen by searching for the split screen divider */
+fun UiDevice.isInSplitScreen(): Boolean {
+    return this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT) != null
+}
+
+fun waitSplitScreenGone(wmHelper: WindowManagerStateHelper) {
+    return wmHelper
+        .StateSyncBuilder()
+        .add(ConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER).negate())
+        .withAppTransitionIdle()
+        .waitForAndVerify()
+}
+
+private val splitScreenDividerSelector: BySelector
+    get() = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle")
+
+/**
+ * Drags the split screen divider to the top of the screen to close it
+ *
+ * @throws AssertionError when unable to find the split screen divider
+ */
+fun UiDevice.exitSplitScreen() {
+    // Quickstep enabled
+    val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find Split screen divider", divider)
+
+    // Drag the split screen divider to the top of the screen
+    val dstPoint =
+        if (this.isRotated()) {
+            Point(0, this.displayWidth / 2)
+        } else {
+            Point(this.displayWidth / 2, 0)
+        }
+    divider.drag(dstPoint, 400)
+    // Wait for animation to complete.
+    SystemClock.sleep(2000)
+}
+
+/**
+ * Drags the split screen divider to the bottom of the screen to close it
+ *
+ * @throws AssertionError when unable to find the split screen divider
+ */
+fun UiDevice.exitSplitScreenFromBottom(wmHelper: WindowManagerStateHelper) {
+    // Quickstep enabled
+    val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find Split screen divider", divider)
+
+    // Drag the split screen divider to the bottom of the screen
+    val dstPoint =
+        if (this.isRotated()) {
+            Point(this.displayWidth, this.displayWidth / 2)
+        } else {
+            Point(this.displayWidth / 2, this.displayHeight)
+        }
+    divider.drag(dstPoint, 400)
+    waitSplitScreenGone(wmHelper)
+}
+
+/**
+ * Drags the split screen divider to resize the windows in split screen
+ *
+ * @throws AssertionError when unable to find the split screen divider
+ */
+fun UiDevice.resizeSplitScreen(windowHeightRatio: Rational) {
+    val dividerSelector = splitScreenDividerSelector
+    val divider = this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
+    assertNotNull("Unable to find Split screen divider", divider)
+    val destHeight = (displayBounds.height() * windowHeightRatio.toFloat()).toInt()
+
+    // Drag the split screen divider to so that the ratio of top window height and bottom
+    // window height is windowHeightRatio
+    this.drag(
+        divider.visibleBounds.centerX(),
+        divider.visibleBounds.centerY(),
+        this.displayWidth / 2,
+        destHeight,
+        10
+    )
+    this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
+    // Wait for animation to complete.
+    SystemClock.sleep(2000)
+}
+
+/** Checks if the device has a window with the package name */
+fun UiDevice.hasWindow(packageName: String): Boolean {
+    return this.wait(Until.findObject(By.pkg(packageName)), FIND_TIMEOUT) != null
+}
+
+/** Waits until the package with that name is gone */
+fun UiDevice.waitUntilGone(packageName: String): Boolean {
+    return this.wait(Until.gone(By.pkg(packageName)), FIND_TIMEOUT) != null
+}
+
+fun stopPackage(context: Context, packageName: String) {
+    executeShellCommand("am force-stop $packageName")
+    val packageUid =
+        try {
+            context.packageManager.getPackageUid(packageName, /* flags */ 0)
+        } catch (e: PackageManager.NameNotFoundException) {
+            return
+        }
+    while (targetPackageIsRunning(packageUid)) {
+        try {
+            Thread.sleep(100)
+        } catch (e: InterruptedException) { // ignore
+        }
+    }
+}
+
+private fun targetPackageIsRunning(uid: Int): Boolean {
+    val result = String(executeShellCommand("cmd activity get-uid-state $uid"))
+    return !result.contains("(NONEXISTENT)")
+}
+
+/** Turns on the device display and presses the home button to reach the launcher screen */
+fun UiDevice.wakeUpAndGoToHomeScreen() {
+    try {
+        this.wakeUp()
+    } catch (e: RemoteException) {
+        throw RuntimeException(e)
+    }
+    this.pressHome()
+}
diff --git a/libraries/flicker/src/android/tools/device/helpers/RotationUtils.kt b/libraries/flicker/src/android/tools/device/helpers/RotationUtils.kt
new file mode 100644
index 0000000..99ee197
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/helpers/RotationUtils.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.device.helpers
+
+import android.graphics.Insets
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+
+/**
+ * A class containing utility methods related to rotation.
+ *
+ * @hide
+ */
+object RotationUtils {
+    /** Rotates an Insets according to the given rotation. */
+    fun rotateInsets(insets: Insets?, rotation: Rotation): Insets {
+        if (insets == null || insets === Insets.NONE) {
+            return Insets.NONE
+        }
+        val rotated =
+            when (rotation) {
+                Rotation.ROTATION_0 -> insets
+                Rotation.ROTATION_90 ->
+                    Insets.of(insets.top, insets.right, insets.bottom, insets.left)
+                Rotation.ROTATION_180 ->
+                    Insets.of(insets.right, insets.bottom, insets.left, insets.top)
+                Rotation.ROTATION_270 ->
+                    Insets.of(insets.bottom, insets.left, insets.top, insets.right)
+            }
+        return rotated
+    }
+
+    /**
+     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated from
+     * oldRotation to newRotation. This assumes that parentBounds is at 0,0 and remains at 0,0 after
+     * rotation. The bounds will be at the same physical position in parentBounds.
+     */
+    fun rotateBounds(
+        inBounds: Rect,
+        parentBounds: Rect,
+        oldRotation: Rotation,
+        newRotation: Rotation
+    ): Rect = rotateBounds(inBounds, parentBounds, deltaRotation(oldRotation, newRotation))
+
+    /**
+     * Rotates inOutBounds together with the parent for a given rotation delta. This assumes that
+     * the parent starts at 0,0 and remains at 0,0 after the rotation. The inOutBounds will remain
+     * at the same physical position within the parent.
+     */
+    fun rotateBounds(
+        inBounds: Rect,
+        parentWidth: Int,
+        parentHeight: Int,
+        rotation: Rotation
+    ): Rect {
+        val origLeft = inBounds.left
+        val origTop = inBounds.top
+        return when (rotation) {
+            Rotation.ROTATION_0 -> inBounds
+            Rotation.ROTATION_90 ->
+                Rect.from(
+                    left = inBounds.top,
+                    top = parentWidth - inBounds.right,
+                    right = inBounds.bottom,
+                    bottom = parentWidth - origLeft
+                )
+            Rotation.ROTATION_180 ->
+                Rect.from(
+                    left = parentWidth - inBounds.right,
+                    right = parentWidth - origLeft,
+                    top = parentHeight - inBounds.bottom,
+                    bottom = parentHeight - origTop
+                )
+            Rotation.ROTATION_270 ->
+                Rect.from(
+                    left = parentHeight - inBounds.bottom,
+                    bottom = inBounds.right,
+                    right = parentHeight - inBounds.top,
+                    top = origLeft
+                )
+        }
+    }
+
+    /**
+     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
+     * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and remains
+     * at 0,0 after rotation. The bounds will be at the same physical position in parentBounds.
+     */
+    fun rotateBounds(inBounds: Rect, parentBounds: Rect, rotation: Rotation): Rect =
+        rotateBounds(inBounds, parentBounds.right, parentBounds.bottom, rotation)
+
+    /** @return the rotation needed to rotate from oldRotation to newRotation. */
+    fun deltaRotation(oldRotation: Rotation, newRotation: Rotation): Rotation {
+        var delta = newRotation.value - oldRotation.value
+        if (delta < 0) delta += 4
+        return Rotation.getByValue(delta)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/helpers/WindowUtils.kt b/libraries/flicker/src/android/tools/device/helpers/WindowUtils.kt
new file mode 100644
index 0000000..08bc102
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/helpers/WindowUtils.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.device.helpers
+
+import android.graphics.Rect
+import android.tools.common.PlatformConsts
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Region
+import android.tools.common.traces.surfaceflinger.Display
+import android.tools.common.traces.wm.DisplayContent
+import android.tools.device.traces.getCurrentStateDump
+import android.tools.device.traces.parsers.toAndroidRect
+import android.util.LruCache
+import androidx.test.platform.app.InstrumentationRegistry
+
+object WindowUtils {
+
+    private val displayBoundsCache = LruCache<Rotation, Region>(1)
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    /** Helper functions to retrieve system window sizes and positions. */
+    private val context = instrumentation.context
+
+    private val resources
+        get() = context.getResources()
+
+    /** Get the display bounds */
+    val displayBounds: Rect
+        get() {
+            val currState = getCurrentStateDump(clearCacheAfterParsing = false)
+            return currState.layerState.physicalDisplay?.layerStackSpace?.toAndroidRect() ?: Rect()
+        }
+
+    /** Gets the current display rotation */
+    val displayRotation: Rotation
+        get() {
+            val currState = getCurrentStateDump(clearCacheAfterParsing = false)
+
+            return currState.wmState.getRotation(PlatformConsts.DEFAULT_DISPLAY)
+        }
+
+    /**
+     * Get the display bounds when the device is at a specific rotation
+     *
+     * @param requestedRotation Device rotation
+     */
+    fun getDisplayBounds(requestedRotation: Rotation): Region {
+        return displayBoundsCache[requestedRotation]
+            ?: let {
+                val displayIsRotated = displayRotation.isRotated()
+                val requestedDisplayIsRotated = requestedRotation.isRotated()
+
+                // if the current orientation changes with the requested rotation,
+                // flip height and width of display bounds.
+                val displayBounds = displayBounds
+                val retval: Region
+                if (displayIsRotated != requestedDisplayIsRotated) {
+                    retval = Region.from(0, 0, displayBounds.height(), displayBounds.width())
+                } else {
+                    retval = Region.from(0, 0, displayBounds.width(), displayBounds.height())
+                }
+                displayBoundsCache.put(requestedRotation, retval)
+                return retval
+            }
+    }
+    /** Gets the status bar height with a specific display cutout. */
+    private fun getExpectedStatusBarHeight(displayContent: DisplayContent): Int {
+        val cutout = displayContent.cutout
+        val defaultSize = status_bar_height_default
+        val safeInsetTop = cutout?.insets?.top ?: 0
+        val waterfallInsetTop = cutout?.waterfallInsets?.top ?: 0
+        // The status bar height should be:
+        // Max(top cutout size, (status bar default height + waterfall top size))
+        return safeInsetTop.coerceAtLeast(defaultSize + waterfallInsetTop)
+    }
+
+    /**
+     * Gets the expected status bar position for a specific display
+     *
+     * @param display the main display
+     */
+    fun getExpectedStatusBarPosition(display: DisplayContent): Region {
+        val height = getExpectedStatusBarHeight(display)
+        return Region.from(0, 0, display.displayRect.width, height)
+    }
+
+    /**
+     * Gets the expected navigation bar position for a specific display
+     *
+     * @param display the main display
+     */
+    fun getNavigationBarPosition(display: Display): Region {
+        return getNavigationBarPosition(display, isGesturalNavigationEnabled)
+    }
+
+    /**
+     * Gets the expected navigation bar position for a specific display
+     *
+     * @param display the main display
+     * @param isGesturalNavigation whether gestural navigation is enabled
+     */
+    fun getNavigationBarPosition(display: Display, isGesturalNavigation: Boolean): Region {
+        val navBarWidth = getDimensionPixelSize("navigation_bar_width")
+        val displayHeight = display.layerStackSpace.height
+        val displayWidth = display.layerStackSpace.width
+        val requestedRotation = display.transform.getRotation()
+        val navBarHeight = getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation)
+
+        return when {
+            // nav bar is at the bottom of the screen
+            !requestedRotation.isRotated() || isGesturalNavigation ->
+                Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
+            // nav bar is on the right side
+            requestedRotation == Rotation.ROTATION_90 ->
+                Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
+            // nav bar is on the left side
+            requestedRotation == Rotation.ROTATION_270 ->
+                Region.from(0, 0, navBarWidth, displayHeight)
+            else -> error("Unknown rotation $requestedRotation")
+        }
+    }
+
+    /**
+     * Estimate the navigation bar position at a specific rotation
+     *
+     * @param requestedRotation Device rotation
+     */
+    fun estimateNavigationBarPosition(requestedRotation: Rotation): Region {
+        val displayBounds = displayBounds
+        val displayWidth: Int
+        val displayHeight: Int
+        if (!requestedRotation.isRotated()) {
+            displayWidth = displayBounds.width()
+            displayHeight = displayBounds.height()
+        } else {
+            // swap display dimensions in landscape or seascape mode
+            displayWidth = displayBounds.height()
+            displayHeight = displayBounds.width()
+        }
+        val navBarWidth = getDimensionPixelSize("navigation_bar_width")
+        val navBarHeight =
+            getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation = false)
+
+        return when {
+            // nav bar is at the bottom of the screen
+            !requestedRotation.isRotated() || isGesturalNavigationEnabled ->
+                Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
+            // nav bar is on the right side
+            requestedRotation == Rotation.ROTATION_90 ->
+                Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
+            // nav bar is on the left side
+            requestedRotation == Rotation.ROTATION_270 ->
+                Region.from(0, 0, navBarWidth, displayHeight)
+            else -> error("Unknown rotation $requestedRotation")
+        }
+    }
+
+    /** Checks if the device uses gestural navigation */
+    val isGesturalNavigationEnabled: Boolean
+        get() {
+            val resourceId =
+                resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
+            return resources.getInteger(resourceId) == 2 /* NAV_BAR_MODE_GESTURAL */
+        }
+
+    fun getDimensionPixelSize(resourceName: String): Int {
+        val resourceId = resources.getIdentifier(resourceName, "dimen", "android")
+        return resources.getDimensionPixelSize(resourceId)
+    }
+
+    /** Gets the navigation bar frame height */
+    fun getNavigationBarFrameHeight(rotation: Rotation, isGesturalNavigation: Boolean): Int {
+        return if (rotation.isRotated()) {
+            if (isGesturalNavigation) {
+                getDimensionPixelSize("navigation_bar_frame_height")
+            } else {
+                getDimensionPixelSize("navigation_bar_height_landscape")
+            }
+        } else {
+            getDimensionPixelSize("navigation_bar_frame_height")
+        }
+    }
+
+    private val status_bar_height_default: Int
+        get() {
+            val resourceId =
+                resources.getIdentifier("status_bar_height_default", "dimen", "android")
+            return resources.getDimensionPixelSize(resourceId)
+        }
+
+    val quick_qs_offset_height: Int
+        get() {
+            val resourceId = resources.getIdentifier("quick_qs_offset_height", "dimen", "android")
+            return resources.getDimensionPixelSize(resourceId)
+        }
+
+    /** Split screen divider inset height */
+    val dockedStackDividerInset: Int
+        get() {
+            val resourceId =
+                resources.getIdentifier("docked_stack_divider_insets", "dimen", "android")
+            return resources.getDimensionPixelSize(resourceId)
+        }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/Consts.kt b/libraries/flicker/src/android/tools/device/traces/Consts.kt
new file mode 100644
index 0000000..b3bb279
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/Consts.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("Consts")
+
+package android.tools.device.traces
+
+import android.os.Trace
+import android.tools.common.LoggerBuilder
+import android.util.Log
+
+internal const val LOG_TAG = "FLICKER-PARSER"
+
+val ANDROID_LOGGER =
+    LoggerBuilder()
+        .setD { tag, msg -> Log.d(tag, msg) }
+        .setI { tag, msg -> Log.i(tag, msg) }
+        .setW { tag, msg -> Log.w(tag, msg) }
+        .setE { tag, msg, error -> Log.e(tag, msg, error) }
+        .setOnTracing { name, predicate ->
+            try {
+                Trace.beginSection(name)
+                predicate()
+            } finally {
+                Trace.endSection()
+            }
+        }
+        .build()
+
+val DEFAULT_TRACE_CONFIG =
+    TraceConfigs(
+        wmTrace = TraceConfig(required = true, allowNoChange = false, usingExistingTraces = false),
+        layersTrace =
+            TraceConfig(required = true, allowNoChange = false, usingExistingTraces = false),
+        transitionsTrace =
+            TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
+        transactionsTrace =
+            TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false)
+    )
diff --git a/libraries/flicker/src/android/tools/device/traces/Extensions.kt b/libraries/flicker/src/android/tools/device/traces/Extensions.kt
new file mode 100644
index 0000000..e23fade
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/Extensions.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("Extensions")
+
+package android.tools.device.traces
+
+import android.os.SystemClock
+import android.tools.common.CrossPlatform
+import android.tools.common.SECOND_AS_NANOSECONDS
+import android.tools.common.Timestamp
+import java.io.File
+import java.time.Instant
+
+/**
+ * Gets the default flicker output dir. By default, the data is stored in /sdcard/flicker instead of
+ * using the app's internal data directory to be accessible by other components (i.e. FilePuller)
+ */
+fun getDefaultFlickerOutputDir() = File("/sdcard/flicker")
+
+/** @return the current timestamp as [Timestamp] */
+fun now(): Timestamp {
+    val now = Instant.now()
+    return CrossPlatform.timestamp.from(
+        elapsedNanos = SystemClock.elapsedRealtimeNanos(),
+        systemUptimeNanos = SystemClock.uptimeNanos(),
+        unixNanos = now.epochSecond * SECOND_AS_NANOSECONDS + now.nano
+    )
+}
+
+fun File.deleteIfExists(): Boolean =
+    if (this.exists()) {
+        this.delete()
+    } else {
+        false
+    }
diff --git a/libraries/flicker/src/android/tools/device/traces/TraceConfig.kt b/libraries/flicker/src/android/tools/device/traces/TraceConfig.kt
new file mode 100644
index 0000000..108b861
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/TraceConfig.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.device.traces
+
+data class TraceConfig(
+    var required: Boolean,
+    var allowNoChange: Boolean,
+    var usingExistingTraces: Boolean
+)
diff --git a/libraries/flicker/src/android/tools/device/traces/TraceConfigs.kt b/libraries/flicker/src/android/tools/device/traces/TraceConfigs.kt
new file mode 100644
index 0000000..24524a7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/TraceConfigs.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.device.traces
+
+data class TraceConfigs(
+    val wmTrace: TraceConfig,
+    val layersTrace: TraceConfig,
+    val transitionsTrace: TraceConfig,
+    val transactionsTrace: TraceConfig
+) {
+    fun applyToAll(function: (TraceConfig) -> Unit) {
+        function(wmTrace)
+        function(layersTrace)
+        function(transitionsTrace)
+        function(transactionsTrace)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/Utils.kt b/libraries/flicker/src/android/tools/device/traces/Utils.kt
new file mode 100644
index 0000000..566e11b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/Utils.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("Utils")
+
+package android.tools.device.traces
+
+import android.app.UiAutomation
+import android.os.ParcelFileDescriptor
+import android.tools.common.CrossPlatform
+import android.tools.common.MILLISECOND_AS_NANOSECONDS
+import android.tools.common.io.TraceType
+import android.tools.common.traces.DeviceStateDump
+import android.tools.common.traces.NullableDeviceStateDump
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.device.traces.parsers.DeviceDumpParser
+import androidx.test.platform.app.InstrumentationRegistry
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+
+fun formatRealTimestamp(timestampNs: Long): String {
+    val timestampMs = timestampNs / MILLISECOND_AS_NANOSECONDS
+    val remainderNs = timestampNs % MILLISECOND_AS_NANOSECONDS
+    val date = Date(timestampMs)
+
+    val timeFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH)
+    timeFormatter.timeZone = TimeZone.getTimeZone("UTC")
+
+    return "${timeFormatter.format(date)}${remainderNs.toString().padStart(6, '0')}"
+}
+
+fun executeShellCommand(cmd: String): ByteArray {
+    val uiAutomation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
+    val fileDescriptor = uiAutomation.executeShellCommand(cmd)
+    ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { inputStream ->
+        return inputStream.readBytes()
+    }
+}
+
+private fun getCurrentWindowManagerState() = executeShellCommand("dumpsys window --proto")
+
+private fun getCurrentLayersState() = executeShellCommand("dumpsys SurfaceFlinger --proto")
+
+/**
+ * Gets the current device state dump containing the [WindowManagerState] (optional) and the
+ * [LayerTraceEntry] (optional) in raw (byte) data.
+ *
+ * @param dumpTypes Flags determining which types of traces should be included in the dump
+ */
+fun getCurrentState(
+    vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
+): Pair<ByteArray, ByteArray> {
+    if (dumpTypes.isEmpty()) {
+        throw IllegalArgumentException("No dump specified")
+    }
+
+    val traceTypes = dumpTypes.filter { it.isTrace }
+    if (traceTypes.isNotEmpty()) {
+        throw IllegalArgumentException("Only dump types are supported. Invalid types: $traceTypes")
+    }
+
+    CrossPlatform.log.d(LOG_TAG, "Requesting new device state dump")
+    val wmTraceData =
+        if (dumpTypes.contains(TraceType.WM_DUMP)) {
+            getCurrentWindowManagerState()
+        } else {
+            ByteArray(0)
+        }
+    val layersTraceData =
+        if (dumpTypes.contains(TraceType.SF_DUMP)) {
+            getCurrentLayersState()
+        } else {
+            ByteArray(0)
+        }
+
+    return Pair(wmTraceData, layersTraceData)
+}
+
+/**
+ * Gets the current device state dump containing the [WindowManagerState] (optional) and the
+ * [LayerTraceEntry] (optional) parsed
+ *
+ * @param dumpTypes Flags determining which types of traces should be included in the dump
+ * @param clearCacheAfterParsing If the caching used while parsing the proto should be
+ * ```
+ *                               cleared or remain in memory
+ * ```
+ */
+@JvmOverloads
+fun getCurrentStateDumpNullable(
+    vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
+    clearCacheAfterParsing: Boolean = true
+): NullableDeviceStateDump {
+    val currentStateDump = getCurrentState(*dumpTypes)
+    return DeviceDumpParser.fromNullableDump(
+        currentStateDump.first,
+        currentStateDump.second,
+        clearCacheAfterParsing = clearCacheAfterParsing
+    )
+}
+
+@JvmOverloads
+fun getCurrentStateDump(
+    vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
+    clearCacheAfterParsing: Boolean = true
+): DeviceStateDump {
+    val currentStateDump = getCurrentState(*dumpTypes)
+    return DeviceDumpParser.fromDump(
+        currentStateDump.first,
+        currentStateDump.second,
+        clearCacheAfterParsing = clearCacheAfterParsing
+    )
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/IResultData.kt b/libraries/flicker/src/android/tools/device/traces/io/IResultData.kt
new file mode 100644
index 0000000..9a7f24e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/IResultData.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.Timestamp
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TransitionTimeRange
+import java.io.File
+
+/** Contents of a flicker run (e.g. files, status, event log) */
+interface IResultData {
+    val transitionTimeRange: TransitionTimeRange
+    val executionError: Throwable?
+    val artifact: File
+    val runStatus: RunStatus
+
+    fun getArtifactBytes(): ByteArray
+
+    /** updates the artifact status to [newStatus] */
+    fun getNewFilePath(newStatus: RunStatus): File {
+        val currTestName = artifact.name.dropWhile { it != '_' }
+        return artifact.resolveSibling("${newStatus.prefix}_$currTestName")
+    }
+
+    /** updates the artifact status to [newStatus] */
+    fun updateStatus(newStatus: RunStatus): IResultData
+
+    /** @return a subsection of the trace */
+    fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): IResultData
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/IoUtils.kt b/libraries/flicker/src/android/tools/device/traces/io/IoUtils.kt
new file mode 100644
index 0000000..a70a7ad
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/IoUtils.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.executeShellCommand
+import java.io.File
+
+object IoUtils {
+    private fun renameFile(src: File, dst: File) {
+        executeShellCommand("mv $src $dst")
+    }
+
+    private fun copyFile(src: File, dst: File) {
+        executeShellCommand("cp $src $dst")
+        executeShellCommand("chmod a+r $dst")
+    }
+
+    fun moveFile(src: File, dst: File) {
+        // Move the  file to the output directory
+        // Note: Due to b/141386109, certain devices do not allow moving the files between
+        //       directories with different encryption policies, so manually copy and then
+        //       remove the original file
+        //       Moreover, the copied trace file may end up with different permissions, resulting
+        //       in b/162072200, to prevent this, ensure the files are readable after copying
+        copyFile(src, dst)
+        executeShellCommand("rm $src")
+    }
+
+    fun addStatusToFileName(traceFile: File, status: RunStatus) {
+        val newFileName = "${status.prefix}_${traceFile.name}"
+        val dst = traceFile.resolveSibling(newFileName)
+        renameFile(traceFile, dst)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/ResultData.kt b/libraries/flicker/src/android/tools/device/traces/io/ResultData.kt
new file mode 100644
index 0000000..70b9c2b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/ResultData.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.Timestamp
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TransitionTimeRange
+import java.io.File
+
+/**
+ * Contents of a flicker run (e.g. files, status, event log)
+ *
+ * @param _artifact Path to the artifact file
+ * @param _transitionTimeRange Transition start and end time
+ * @param _executionError Transition execution error (if any)
+ * @param _runStatus Status of the run
+ */
+open class ResultData(
+    _artifact: File,
+    _transitionTimeRange: TransitionTimeRange,
+    _executionError: Throwable?,
+    _runStatus: RunStatus
+) : IResultData {
+    final override var artifact: File = _artifact
+        private set
+    final override var transitionTimeRange: TransitionTimeRange = _transitionTimeRange
+        private set
+    final override var executionError: Throwable? = _executionError
+        private set
+    final override var runStatus: RunStatus = _runStatus
+        private set
+
+    /** {@inheritDoc} */
+    override fun getArtifactBytes(): ByteArray = artifact.readBytes()
+
+    /** {@inheritDoc} */
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp) = apply {
+        require(startTimestamp.hasAllTimestamps)
+        require(endTimestamp.hasAllTimestamps)
+        return ResultData(
+            artifact,
+            TransitionTimeRange(startTimestamp, endTimestamp),
+            executionError,
+            runStatus
+        )
+    }
+
+    override fun toString(): String = buildString {
+        append(artifact)
+        append(" (status=")
+        append(runStatus)
+        executionError?.let {
+            append(", error=")
+            append(it.message)
+        }
+        append(") ")
+    }
+
+    /** {@inheritDoc} */
+    override fun updateStatus(newStatus: RunStatus) = apply {
+        val currFile = artifact
+        require(RunStatus.fromFileName(currFile.name) != RunStatus.UNDEFINED) {
+            "File name should start with a value from `RunStatus`, instead it was $currFile"
+        }
+        val newFile = getNewFilePath(newStatus)
+        if (currFile != newFile) {
+            IoUtils.moveFile(currFile, newFile)
+            artifact = newFile
+            runStatus = newStatus
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/ResultReader.kt b/libraries/flicker/src/android/tools/device/traces/io/ResultReader.kt
new file mode 100644
index 0000000..c3f39a5
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/ResultReader.kt
@@ -0,0 +1,287 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Tag
+import android.tools.common.Timestamp
+import android.tools.common.io.BUFFER_SIZE
+import android.tools.common.io.FLICKER_IO_TAG
+import android.tools.common.io.IReader
+import android.tools.common.io.ResultArtifactDescriptor
+import android.tools.common.io.TraceType
+import android.tools.common.parsers.events.EventLogParser
+import android.tools.common.traces.events.CujTrace
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.TraceConfig
+import android.tools.device.traces.TraceConfigs
+import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
+import android.tools.device.traces.parsers.surfaceflinger.TransactionsTraceParser
+import android.tools.device.traces.parsers.wm.TransitionsTraceParser
+import android.tools.device.traces.parsers.wm.WindowManagerDumpParser
+import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
+import androidx.annotation.VisibleForTesting
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+
+/**
+ * Helper class to read results from a flicker artifact
+ *
+ * @param result to read from
+ * @param traceConfig
+ */
+open class ResultReader(internal var result: IResultData, internal val traceConfig: TraceConfigs) :
+    IReader {
+    override val artifactPath: String
+        get() = result.artifact.absolutePath
+    override val runStatus
+        get() = result.runStatus
+    internal val transitionTimeRange
+        get() = result.transitionTimeRange
+    override val isFailure
+        get() = runStatus.isFailure
+    override val executionError
+        get() = result.executionError
+
+    private fun withZipFile(predicate: (ZipInputStream) -> Unit) {
+        val zipInputStream =
+            ZipInputStream(
+                BufferedInputStream(ByteArrayInputStream(result.getArtifactBytes()), BUFFER_SIZE)
+            )
+        try {
+            predicate(zipInputStream)
+        } finally {
+            zipInputStream.closeEntry()
+            zipInputStream.close()
+        }
+    }
+
+    private fun forEachFileInZip(predicate: (ZipEntry) -> Unit) {
+        withZipFile {
+            var zipEntry: ZipEntry? = it.nextEntry
+            while (zipEntry != null) {
+                predicate(zipEntry)
+                zipEntry = it.nextEntry
+            }
+        }
+    }
+
+    @Throws(IOException::class)
+    private fun readFromZip(descriptor: ResultArtifactDescriptor): ByteArray? {
+        CrossPlatform.log.d(FLICKER_IO_TAG, "Reading descriptor=$descriptor from $result")
+
+        var foundFile = false
+        val outByteArray = ByteArrayOutputStream()
+        val tmpBuffer = ByteArray(BUFFER_SIZE)
+        withZipFile {
+            var zipEntry: ZipEntry? = it.nextEntry
+            while (zipEntry != null) {
+                if (zipEntry.name == descriptor.fileNameInArtifact) {
+                    val outputStream = BufferedOutputStream(outByteArray, BUFFER_SIZE)
+                    try {
+                        var size = it.read(tmpBuffer, 0, BUFFER_SIZE)
+                        while (size > 0) {
+                            outputStream.write(tmpBuffer, 0, size)
+                            size = it.read(tmpBuffer, 0, BUFFER_SIZE)
+                        }
+                        it.closeEntry()
+                    } finally {
+                        outputStream.flush()
+                        outputStream.close()
+                    }
+                    foundFile = true
+                    break
+                }
+                zipEntry = it.nextEntry
+            }
+        }
+
+        return if (foundFile) outByteArray.toByteArray() else null
+    }
+
+    override fun readBytes(traceType: TraceType, tag: String): ByteArray? =
+        readFromZip(ResultArtifactDescriptor(traceType, tag))
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readWmState(tag: String): WindowManagerTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.WM_DUMP, tag)
+        CrossPlatform.log.d(FLICKER_IO_TAG, "Reading WM trace descriptor=$descriptor from $result")
+        val traceData = readFromZip(descriptor)
+        return traceData?.let { WindowManagerDumpParser().parse(it, clearCache = true) }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readWmTrace(): WindowManagerTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.WM)
+        return readFromZip(descriptor)?.let {
+            val trace =
+                WindowManagerTraceParser()
+                    .parse(
+                        it,
+                        from = transitionTimeRange.start,
+                        to = transitionTimeRange.end,
+                        addInitialEntry = true,
+                        clearCache = true
+                    )
+            val minimumEntries = minimumTraceEntriesForConfig(traceConfig.wmTrace)
+            require(trace.entries.size >= minimumEntries) {
+                "WM trace contained ${trace.entries.size} entries, " +
+                    "expected at least $minimumEntries... :: " +
+                    "transition starts at ${transitionTimeRange.start} and " +
+                    "ends at ${transitionTimeRange.end}."
+            }
+            trace
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readLayersTrace(): LayersTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.SF)
+        return readFromZip(descriptor)?.let {
+            val trace =
+                LayersTraceParser()
+                    .parse(
+                        it,
+                        transitionTimeRange.start,
+                        transitionTimeRange.end,
+                        addInitialEntry = true,
+                        clearCache = true
+                    )
+            val minimumEntries = minimumTraceEntriesForConfig(traceConfig.layersTrace)
+            require(trace.entries.size >= minimumEntries) {
+                "Layers trace contained ${trace.entries.size} entries, " +
+                    "expected at least $minimumEntries... :: " +
+                    "transition starts at ${transitionTimeRange.start} and " +
+                    "ends at ${transitionTimeRange.end}."
+            }
+            trace
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readLayersDump(tag: String): LayersTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.SF_DUMP, tag)
+        val traceData = readFromZip(descriptor)
+        return traceData?.let { LayersTraceParser().parse(it, clearCache = true) }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readTransactionsTrace(): TransactionsTrace? =
+        doReadTransactionsTrace(from = transitionTimeRange.start, to = transitionTimeRange.end)
+
+    private fun doReadTransactionsTrace(from: Timestamp, to: Timestamp): TransactionsTrace? {
+        val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSACTION))
+        return traceData?.let {
+            val trace = TransactionsTraceParser().parse(it, from, to, addInitialEntry = true)
+            require(trace.entries.isNotEmpty()) { "Transactions trace cannot be empty" }
+            trace
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readTransitionsTrace(): TransitionsTrace? {
+        val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSITION)) ?: return null
+
+        val fullTrace = TransitionsTraceParser().parse(traceData)
+        val trace = fullTrace.slice(transitionTimeRange.start, transitionTimeRange.end)
+        if (!traceConfig.transitionsTrace.allowNoChange) {
+            require(trace.entries.isNotEmpty()) { "Transitions trace cannot be empty" }
+        }
+        return trace
+    }
+
+    private fun minimumTraceEntriesForConfig(config: TraceConfig): Int {
+        return if (config.allowNoChange) 1 else 2
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readEventLogTrace(): EventLog? {
+        val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
+        return readFromZip(descriptor)?.let {
+            EventLogParser()
+                .parse(it, from = transitionTimeRange.start, to = transitionTimeRange.end)
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException if the artifact file doesn't exist or can't be read
+     */
+    @Throws(IOException::class)
+    override fun readCujTrace(): CujTrace? = readEventLogTrace()?.cujTrace
+
+    /** @return an [IReader] for the subsection of the trace we are reading in this reader */
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReader {
+        val slicedResult = result.slice(startTimestamp, endTimestamp)
+        return ResultReader(slicedResult, traceConfig)
+    }
+
+    override fun toString(): String = "$result"
+
+    /** @return the number of files in the artifact */
+    @VisibleForTesting
+    fun countFiles(): Int {
+        var count = 0
+        forEachFileInZip { count++ }
+        return count
+    }
+
+    /** @return if a file with type [traceType] linked to a [tag] exists in the artifact */
+    fun hasTraceFile(traceType: TraceType, tag: String = Tag.ALL): Boolean {
+        val descriptor = ResultArtifactDescriptor(traceType, tag)
+        var found = false
+        forEachFileInZip { found = found || (it.name == descriptor.fileNameInArtifact) }
+        return found
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/ResultReaderWithLru.kt b/libraries/flicker/src/android/tools/device/traces/io/ResultReaderWithLru.kt
new file mode 100644
index 0000000..bfbc255
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/ResultReaderWithLru.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.Timestamp
+import android.tools.common.io.IReader
+import android.tools.common.io.ResultArtifactDescriptor
+import android.tools.common.io.TraceType
+import android.tools.common.io.TransitionTimeRange
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.TraceConfigs
+import android.util.LruCache
+import java.io.IOException
+
+/**
+ * Helper class to read results from a flicker artifact using a LRU
+ *
+ * @param result to read from
+ * @param traceConfig
+ */
+open class ResultReaderWithLru(
+    result: IResultData,
+    traceConfig: TraceConfigs,
+    private val reader: ResultReader = ResultReader(result, traceConfig)
+) : IReader by reader {
+    /** {@inheritDoc} */
+    @Throws(IOException::class)
+    override fun readWmTrace(): WindowManagerTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.SF)
+        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
+        val trace = wmTraceCache[key] ?: reader.readWmTrace()
+        return trace?.also { wmTraceCache.put(key, trace) }
+    }
+
+    /** {@inheritDoc} */
+    @Throws(IOException::class)
+    override fun readLayersTrace(): LayersTrace? {
+        val descriptor = ResultArtifactDescriptor(TraceType.SF)
+        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
+        val trace = layersTraceCache[key] ?: reader.readLayersTrace()
+        return trace?.also { layersTraceCache.put(key, trace) }
+    }
+
+    /** {@inheritDoc} */
+    @Throws(IOException::class)
+    override fun readEventLogTrace(): EventLog? {
+        val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
+        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
+        val trace = eventLogCache[key] ?: reader.readEventLogTrace()
+        return trace?.also { eventLogCache.put(key, trace) }
+    }
+
+    /** {@inheritDoc} */
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReaderWithLru {
+        val slicedReader = reader.slice(startTimestamp, endTimestamp)
+        return ResultReaderWithLru(slicedReader.result, slicedReader.traceConfig, slicedReader)
+    }
+
+    companion object {
+        data class CacheKey(
+            private val artifact: String,
+            private val descriptor: ResultArtifactDescriptor,
+            private val transitionTimeRange: TransitionTimeRange
+        )
+
+        private val wmTraceCache = LruCache<CacheKey, WindowManagerTrace>(1)
+        private val layersTraceCache = LruCache<CacheKey, LayersTrace>(1)
+        private val eventLogCache = LruCache<CacheKey, EventLog>(1)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/io/ResultWriter.kt b/libraries/flicker/src/android/tools/device/traces/io/ResultWriter.kt
new file mode 100644
index 0000000..95e5dbe
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/io/ResultWriter.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.ScenarioBuilder
+import android.tools.common.Tag
+import android.tools.common.Timestamp
+import android.tools.common.io.BUFFER_SIZE
+import android.tools.common.io.FLICKER_IO_TAG
+import android.tools.common.io.ResultArtifactDescriptor
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.common.io.TransitionTimeRange
+import android.tools.device.traces.deleteIfExists
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+/** Helper class to create run result artifact files */
+open class ResultWriter {
+    protected var scenario: IScenario = ScenarioBuilder().createEmptyScenario()
+    private var runStatus: RunStatus = RunStatus.UNDEFINED
+    private val files = mutableMapOf<ResultArtifactDescriptor, File>()
+    private var transitionStartTime = CrossPlatform.timestamp.min()
+    private var transitionEndTime = CrossPlatform.timestamp.max()
+    private var executionError: Throwable? = null
+    private var outputDir: File? = null
+
+    /** Sets the artifact scenario to [_scenario] */
+    fun forScenario(_scenario: IScenario) = apply { scenario = _scenario }
+
+    /** Sets the artifact transition start time to [time] */
+    fun setTransitionStartTime(time: Timestamp) = apply { transitionStartTime = time }
+
+    /** Sets the artifact transition end time to [time] */
+    fun setTransitionEndTime(time: Timestamp) = apply { transitionEndTime = time }
+
+    /** Sets the artifact status as successfully executed transition ([RunStatus.RUN_EXECUTED]) */
+    fun setRunComplete() = apply { runStatus = RunStatus.RUN_EXECUTED }
+
+    /** Sets the dir where the artifact file will be stored to [dir] */
+    fun withOutputDir(dir: File) = apply { outputDir = dir }
+
+    /**
+     * Sets the artifact status as failed executed transition ([RunStatus.RUN_FAILED])
+     *
+     * @param error that caused the transition to fail
+     */
+    fun setRunFailed(error: Throwable) = apply {
+        runStatus = RunStatus.RUN_FAILED
+        executionError = error
+    }
+
+    /**
+     * Adds [artifact] to the result artifact
+     *
+     * @param traceType used when adding [artifact] to the result artifact
+     * @param tag used when adding [artifact] to the result artifact
+     */
+    fun addTraceResult(traceType: TraceType, artifact: File, tag: String = Tag.ALL) = apply {
+        CrossPlatform.log.d(
+            FLICKER_IO_TAG,
+            "Add trace result file=$artifact type=$traceType tag=$tag scenario=$scenario"
+        )
+        val fileDescriptor = ResultArtifactDescriptor(traceType, tag)
+        files[fileDescriptor] = artifact
+    }
+
+    private fun addFile(zipOutputStream: ZipOutputStream, artifact: File, nameInArchive: String) {
+        CrossPlatform.log.v(FLICKER_IO_TAG, "Adding $artifact with name $nameInArchive to zip")
+        val fi = FileInputStream(artifact)
+        val inputStream = BufferedInputStream(fi, BUFFER_SIZE)
+        inputStream.use {
+            val entry = ZipEntry(nameInArchive)
+            zipOutputStream.putNextEntry(entry)
+            val data = ByteArray(BUFFER_SIZE)
+            var count: Int = it.read(data, 0, BUFFER_SIZE)
+            while (count != -1) {
+                zipOutputStream.write(data, 0, count)
+                count = it.read(data, 0, BUFFER_SIZE)
+            }
+        }
+        zipOutputStream.closeEntry()
+        artifact.deleteIfExists()
+    }
+
+    private fun createZipFile(file: File): ZipOutputStream {
+        return ZipOutputStream(BufferedOutputStream(FileOutputStream(file), BUFFER_SIZE))
+    }
+
+    /** @return writes the result artifact to disk and returns it */
+    open fun write(): IResultData {
+        return CrossPlatform.log.withTracing("write") {
+            val outputDir = outputDir
+            requireNotNull(outputDir) { "Output dir not configured" }
+            require(!scenario.isEmpty) { "Scenario shouldn't be empty" }
+            // Ensure output directory exists
+            outputDir.mkdirs()
+
+            if (runStatus == RunStatus.UNDEFINED) {
+                CrossPlatform.log.w(FLICKER_IO_TAG, "Writing result with $runStatus run status")
+            }
+
+            val newFileName = "${runStatus.prefix}_$scenario.zip"
+            val dstFile = outputDir.resolve(newFileName)
+            CrossPlatform.log.d(FLICKER_IO_TAG, "Writing artifact file $dstFile")
+            createZipFile(dstFile).use { zipOutputStream ->
+                files.forEach { (descriptor, artifact) ->
+                    addFile(
+                        zipOutputStream,
+                        artifact,
+                        nameInArchive = descriptor.fileNameInArtifact
+                    )
+                }
+            }
+
+            ResultData(
+                dstFile,
+                TransitionTimeRange(transitionStartTime, transitionEndTime),
+                executionError,
+                runStatus
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/ITransitionMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/ITransitionMonitor.kt
new file mode 100644
index 0000000..159968c
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/ITransitionMonitor.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.device.traces.monitors
+
+import android.tools.device.traces.io.ResultWriter
+
+/** Collects test artifacts during a UI transition. */
+interface ITransitionMonitor {
+    /** Starts monitor. */
+    fun start()
+
+    /** Stops monitor and writes the result to a [ResultWriter] */
+    fun stop(writer: ResultWriter)
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/MonitorUtils.kt b/libraries/flicker/src/android/tools/device/traces/monitors/MonitorUtils.kt
new file mode 100644
index 0000000..0cbdc07
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/MonitorUtils.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("MonitorUtils")
+
+package android.tools.device.traces.monitors
+
+import android.tools.common.traces.DeviceTraceDump
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
+import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
+import android.tools.device.traces.parsers.DeviceDumpParser
+import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
+import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
+
+/**
+ * Acquire the [WindowManagerTrace] with the device state changes that happen when executing the
+ * commands defined in the [predicate].
+ *
+ * @param predicate Commands to execute
+ * @throws UnsupportedOperationException If tracing is already activated
+ */
+fun withWMTracing(predicate: () -> Unit): WindowManagerTrace {
+    return WindowManagerTraceParser().parse(WindowManagerTraceMonitor().withTracing(predicate))
+}
+
+/**
+ * Acquire the [LayersTrace] with the device state changes that happen when executing the commands
+ * defined in the [predicate].
+ *
+ * @param traceFlags Flags to indicate tracing level
+ * @param predicate Commands to execute
+ * @throws UnsupportedOperationException If tracing is already activated
+ */
+@JvmOverloads
+fun withSFTracing(
+    traceFlags: Int = LayersTraceMonitor.TRACE_FLAGS,
+    predicate: () -> Unit
+): LayersTrace {
+    return LayersTraceParser().parse(LayersTraceMonitor(traceFlags).withTracing(predicate))
+}
+
+/**
+ * Acquire the [WindowManagerTrace] and [LayersTrace] with the device state changes that happen when
+ * executing the commands defined in the [predicate].
+ *
+ * @param predicate Commands to execute
+ * @throws UnsupportedOperationException If tracing is already activated
+ */
+fun withTracing(predicate: () -> Unit): DeviceTraceDump {
+    val traces = recordTraces(predicate)
+    val wmTraceData = traces.first
+    val layersTraceData = traces.second
+    return DeviceDumpParser.fromTrace(wmTraceData, layersTraceData, clearCache = true)
+}
+
+/**
+ * Acquire the [WindowManagerTrace] and [LayersTrace] with the device state changes that happen when
+ * executing the commands defined in the [predicate].
+ *
+ * @param predicate Commands to execute
+ * @throws UnsupportedOperationException If tracing is already activated
+ * @return a pair containing the WM and SF traces
+ */
+fun recordTraces(predicate: () -> Unit): Pair<ByteArray, ByteArray> {
+    var wmTraceData = ByteArray(0)
+    val layersTraceData =
+        LayersTraceMonitor().withTracing {
+            wmTraceData = WindowManagerTraceMonitor().withTracing(predicate)
+        }
+
+    return Pair(wmTraceData, layersTraceData)
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/NoTraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/NoTraceMonitor.kt
new file mode 100644
index 0000000..39f1283
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/NoTraceMonitor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.device.traces.monitors
+
+import android.tools.common.CrossPlatform
+import android.tools.device.traces.io.ResultWriter
+
+/**
+ * A monitor that doesn't actually collect any traces and instead get the resultSetter sets the
+ * trace file directly when called.
+ */
+class NoTraceMonitor(private val resultSetter: (ResultWriter) -> Unit) : ITransitionMonitor {
+    override fun start() {
+        // Does nothing
+    }
+
+    override fun stop(writer: ResultWriter) {
+        writer.setTransitionStartTime(CrossPlatform.timestamp.min())
+        writer.setTransitionEndTime(CrossPlatform.timestamp.max())
+        this.resultSetter.invoke(writer)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecorder.kt b/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecorder.kt
new file mode 100644
index 0000000..29b19ba
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecorder.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.device.traces.monitors
+
+import android.content.Context
+import android.os.SystemClock
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.io.TraceType
+import androidx.annotation.VisibleForTesting
+import java.io.File
+
+/** Captures screen contents and saves it as a mp4 video file. */
+open class ScreenRecorder
+@JvmOverloads
+constructor(
+    private val context: Context,
+    private val outputFile: File = File.createTempFile("transition", "screen_recording"),
+    private val width: Int = 720,
+    private val height: Int = 1280
+) : TraceMonitor() {
+    override val traceType = TraceType.SCREEN_RECORDING
+
+    private var recordingThread: Thread? = null
+    private var recordingRunnable: ScreenRecordingRunnable? = null
+
+    private fun newRecordingThread(): Thread {
+        val runnable = ScreenRecordingRunnable(outputFile, context, width, height)
+        recordingRunnable = runnable
+        return Thread(runnable)
+    }
+
+    /** Indicates if any frame has been recorded. */
+    @VisibleForTesting
+    val isFrameRecorded: Boolean
+        get() =
+            when {
+                !isEnabled -> false
+                else -> recordingRunnable?.isFrameRecorded ?: false
+            }
+
+    override fun start() {
+        require(recordingThread == null) { "Screen recorder already running" }
+
+        val recordingThread = newRecordingThread()
+        this.recordingThread = recordingThread
+        CrossPlatform.log.d(FLICKER_TAG, "Starting screen recording thread")
+        recordingThread.start()
+
+        var remainingTime = WAIT_TIMEOUT_MS
+        do {
+            SystemClock.sleep(WAIT_INTERVAL_MS)
+            remainingTime -= WAIT_INTERVAL_MS
+        } while (recordingRunnable?.isFrameRecorded != true)
+
+        require(outputFile.exists()) { "Screen recorder didn't start" }
+    }
+
+    override fun doStop(): File {
+        require(recordingThread != null) { "Screen recorder was not started" }
+
+        CrossPlatform.log.d(FLICKER_TAG, "Stopping screen recording. Storing result in $outputFile")
+        try {
+            recordingRunnable?.stop()
+            recordingThread?.join()
+        } catch (e: Exception) {
+            throw IllegalStateException("Unable to stop screen recording", e)
+        } finally {
+            recordingRunnable = null
+            recordingThread = null
+        }
+        return outputFile
+    }
+
+    override val isEnabled
+        get() = recordingThread != null
+
+    override fun toString(): String {
+        return "ScreenRecorder($outputFile)"
+    }
+
+    companion object {
+        private const val WAIT_TIMEOUT_MS = 5000L
+        private const val WAIT_INTERVAL_MS = 500L
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecordingRunnable.kt b/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecordingRunnable.kt
new file mode 100644
index 0000000..c58d3df
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/ScreenRecordingRunnable.kt
@@ -0,0 +1,238 @@
+/*
+ * 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.device.traces.monitors
+
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.media.MediaCodec
+import android.media.MediaCodecInfo
+import android.media.MediaFormat
+import android.media.MediaMuxer
+import android.os.SystemClock
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.device.traces.deleteIfExists
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.util.concurrent.TimeUnit
+
+/** Runnable to record the screen contents and winscope metadata */
+class ScreenRecordingRunnable(
+    private val outputFile: File,
+    context: Context,
+    private val width: Int = 720,
+    private val height: Int = 1280
+) : Runnable {
+    private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+    private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var finished = false
+    internal var isFrameRecorded = false
+
+    private val metrics: DisplayMetrics
+        get() {
+            val metrics = DisplayMetrics()
+            windowManager.defaultDisplay.getRealMetrics(metrics)
+            return metrics
+        }
+
+    private val encoder = createEncoder()
+    private val inputSurface = encoder.createInputSurface()
+    private val virtualDisplay =
+        displayManager.createVirtualDisplay(
+            "Recording Display",
+            width,
+            height,
+            metrics.densityDpi,
+            inputSurface,
+            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+            null,
+            null
+        )
+    private val muxer = createMuxer()
+    private var metadataTrackIndex = -1
+    private var videoTrackIndex = -1
+
+    internal fun stop() {
+        encoder.signalEndOfInputStream()
+        finished = true
+    }
+
+    override fun run() {
+        CrossPlatform.log.d(FLICKER_TAG, "Starting screen recording to file $outputFile")
+
+        val timestampsUs = mutableListOf<Long>()
+        try {
+            // Start encoder and muxer
+            encoder.start()
+            val bufferInfo = MediaCodec.BufferInfo()
+
+            while (true) {
+                val bufferIndex = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_MS)
+                if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    prepareMuxer()
+                } else if (bufferIndex >= 0) {
+                    val elapsedTimeUs = writeSample(bufferIndex, bufferInfo)
+                    val endOfStream = bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
+                    // end of the stream samples have 0 timestamp
+                    if (endOfStream > 0) {
+                        break
+                    } else {
+                        timestampsUs.add(elapsedTimeUs)
+                    }
+                }
+            }
+        } finally {
+            writeMetadata(timestampsUs)
+            encoder.stop()
+            muxer.stop()
+            muxer.release()
+            encoder.release()
+            inputSurface.release()
+            virtualDisplay.release()
+        }
+    }
+
+    /**
+     * Fetches a sample from the encoder and writes it to the video file
+     *
+     * @return sample timestamp (or 0 for invalid buffers)
+     */
+    private fun writeSample(bufferIndex: Int, bufferInfo: MediaCodec.BufferInfo): Long {
+        val data = encoder.getOutputBuffer(bufferIndex)
+        return if (data != null) {
+            val endOfStream = bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
+
+            if (endOfStream == 0) {
+                val outputBuffer =
+                    encoder.getOutputBuffer(bufferIndex) ?: error("Unable to acquire next frame")
+
+                muxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo)
+                isFrameRecorded = true
+            }
+            encoder.releaseOutputBuffer(bufferIndex, /* render */ false)
+            bufferInfo.presentationTimeUs
+        } else {
+            0
+        }
+    }
+
+    private fun prepareMuxer() {
+        videoTrackIndex = muxer.addTrack(encoder.outputFormat)
+        val metadataFormat = MediaFormat()
+        metadataFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE_METADATA)
+        metadataTrackIndex = muxer.addTrack(metadataFormat)
+        muxer.start()
+    }
+
+    /**
+     * Saves metadata needed by Winscope to synchronize the screen recording playback with other
+     * traces.
+     *
+     * The metadata (version 2) is written as a binary array with the following format:
+     * - winscope magic string (#VV1NSC0PET1ME2#, 16B).
+     * - the metadata version number (4B little endian).
+     * - Realtime-to-elapsed time offset in nanoseconds (8B little endian).
+     * - the recorded frames count (4B little endian)
+     * - for each recorded frame:
+     * ```
+     *     - System time in elapsed clock timebase in nanoseconds (8B little endian).
+     * ```
+     */
+    private fun writeMetadata(timestampsUs: List<Long>) {
+        if (timestampsUs.isEmpty()) {
+            CrossPlatform.log.v(FLICKER_TAG, "Not writing winscope metadata (no frames/timestamps)")
+            return
+        }
+
+        CrossPlatform.log.v(
+            FLICKER_TAG,
+            "Writing winscope metadata (size=${timestampsUs.size} " +
+                "(timestamps [us] = ${timestampsUs.first()}-${timestampsUs.last()})"
+        )
+
+        val timeOffsetNs =
+            TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) -
+                SystemClock.elapsedRealtimeNanos()
+
+        val bufferSize =
+            WINSCOPE_MAGIC_STRING.toByteArray().size +
+                Int.SIZE_BYTES +
+                Long.SIZE_BYTES +
+                Int.SIZE_BYTES +
+                (timestampsUs.size * Long.SIZE_BYTES)
+
+        val buffer =
+            ByteBuffer.allocate(bufferSize)
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .put(WINSCOPE_MAGIC_STRING.toByteArray())
+                .putInt(WINSCOPE_METADATA_VERSION)
+                .putLong(timeOffsetNs)
+                .putInt(timestampsUs.size)
+                .apply { timestampsUs.forEach { putLong(TimeUnit.MICROSECONDS.toNanos(it)) } }
+
+        val bufferInfo = MediaCodec.BufferInfo()
+        bufferInfo.size = bufferSize
+        bufferInfo.presentationTimeUs = timestampsUs[0]
+        muxer.writeSampleData(metadataTrackIndex, buffer, bufferInfo)
+    }
+
+    /**
+     * Create and configure a MediaCodec encoder with [MIME_TYPE_VIDEO] format.
+     *
+     * @return a Surface that can be used to record
+     */
+    private fun createEncoder(): MediaCodec {
+        val format = MediaFormat.createVideoFormat(MIME_TYPE_VIDEO, width, height)
+        val displayMode = windowManager.defaultDisplay.mode
+        format.setInteger(
+            MediaFormat.KEY_COLOR_FORMAT,
+            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
+        )
+        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
+        format.setFloat(MediaFormat.KEY_FRAME_RATE, displayMode.refreshRate)
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL)
+        format.setInteger(MediaFormat.KEY_WIDTH, width)
+        format.setInteger(MediaFormat.KEY_HEIGHT, height)
+        format.setString(MediaFormat.KEY_MIME, MIME_TYPE_VIDEO)
+
+        val mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE_VIDEO)
+        mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
+        return mediaCodec
+    }
+
+    private fun createMuxer(): MediaMuxer {
+        outputFile.deleteIfExists()
+        require(!outputFile.exists())
+        outputFile.createNewFile()
+        val inputStream = FileOutputStream(outputFile)
+        return MediaMuxer(inputStream.fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
+    }
+
+    companion object {
+        private const val WINSCOPE_MAGIC_STRING = "#VV1NSC0PET1ME2#"
+        private const val WINSCOPE_METADATA_VERSION = 2
+        private const val MIME_TYPE_VIDEO = MediaFormat.MIMETYPE_VIDEO_AVC
+        private const val MIME_TYPE_METADATA = "application/octet-stream"
+        private const val BIT_RATE = 2000000 // 2Mbps
+        private const val IFRAME_INTERVAL = 2 // 2 second between I-frames
+        private const val TIMEOUT_MS = 100L
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/TraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/TraceMonitor.kt
new file mode 100644
index 0000000..7fbbb60
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/TraceMonitor.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.device.traces.monitors
+
+import android.tools.common.ScenarioBuilder
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.io.IoUtils
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.io.ResultWriter
+import java.io.File
+
+/**
+ * Base class for monitors containing common logic to read the trace as a byte array and save the
+ * trace to another location.
+ */
+abstract class TraceMonitor : ITransitionMonitor {
+    abstract val isEnabled: Boolean
+    abstract val traceType: TraceType
+    protected abstract fun doStop(): File
+
+    /** Stops monitor. */
+    final override fun stop(writer: ResultWriter) {
+        val artifact =
+            try {
+                val srcFile = doStop()
+                moveTraceFileToTmpDir(srcFile)
+            } catch (e: Throwable) {
+                throw RuntimeException("Could not stop trace", e)
+            }
+        writer.addTraceResult(traceType, artifact)
+    }
+
+    private fun moveTraceFileToTmpDir(sourceFile: File): File {
+        val newFile = File.createTempFile(sourceFile.name, "")
+        IoUtils.moveFile(sourceFile, newFile)
+        require(newFile.exists()) { "Unable to save trace file $newFile" }
+        return newFile
+    }
+
+    /**
+     * Acquires the trace generated when executing the commands defined in the [predicate].
+     *
+     * @param predicate Commands to execute
+     * @throws UnsupportedOperationException If tracing is already activated
+     */
+    fun withTracing(predicate: () -> Unit): ByteArray {
+        if (this.isEnabled) {
+            throw UnsupportedOperationException(
+                "Trace already running. " + "This is likely due to chained 'withTracing' calls."
+            )
+        }
+        val result: IResultData
+        try {
+            this.start()
+            predicate()
+        } finally {
+            val writer = createWriter()
+            this.stop(writer)
+            result = writer.write()
+        }
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val bytes = reader.readBytes(traceType) ?: error("Missing trace $traceType")
+        result.artifact.deleteIfExists()
+        return bytes
+    }
+
+    private fun createWriter(): ResultWriter {
+        val className = this::class.simpleName ?: error("Missing class name for $this")
+        val scenario = ScenarioBuilder().forClass(className).build()
+        val tmpDir = File.createTempFile("withTracing", className).parentFile
+        return ResultWriter().forScenario(scenario).withOutputDir(tmpDir)
+    }
+
+    companion object {
+        @JvmStatic protected val TRACE_DIR = File("/data/misc/wmtrace/")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/events/EventLogMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/events/EventLogMonitor.kt
new file mode 100644
index 0000000..9307e27
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/events/EventLogMonitor.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.device.traces.monitors.events
+
+import android.tools.common.CrossPlatform
+import android.tools.common.FLICKER_TAG
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+import android.tools.common.traces.events.EventLog
+import android.tools.device.traces.executeShellCommand
+import android.tools.device.traces.monitors.TraceMonitor
+import android.tools.device.traces.now
+import java.io.File
+import java.io.FileOutputStream
+
+/** Collects event logs during transitions. */
+open class EventLogMonitor : TraceMonitor() {
+    override val traceType = TraceType.EVENT_LOG
+    final override var isEnabled = false
+        private set
+
+    private var traceStartTime: Timestamp? = null
+
+    override fun start() {
+        require(!isEnabled) { "Trace already running" }
+        isEnabled = true
+        traceStartTime = now()
+    }
+
+    override fun doStop(): File {
+        require(isEnabled) { "Trace not running" }
+        isEnabled = false
+        val sinceTime = traceStartTime?.unixNanosToLogFormat() ?: error("Missing start timestamp")
+
+        traceStartTime = null
+        val outputFile = File.createTempFile(TraceType.EVENT_LOG.fileName, "")
+
+        FileOutputStream(outputFile).use {
+            it.write("${EventLog.MAGIC_NUMBER}\n".toByteArray())
+            val command =
+                "logcat -b events -v threadtime -v printable -v uid -v nsec " +
+                    "-v epoch -t $sinceTime >> $outputFile"
+            CrossPlatform.log.d(FLICKER_TAG, "Running '$command'")
+            val eventLogString = executeShellCommand(command)
+            it.write(eventLogString)
+        }
+
+        return outputFile
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitor.kt
new file mode 100644
index 0000000..1c4e7ac
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitor.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.device.traces.monitors.surfaceflinger
+
+import android.tools.common.io.TraceType
+import android.tools.common.io.WINSCOPE_EXT
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.device.traces.monitors.TraceMonitor
+import android.view.WindowManagerGlobal
+import java.io.File
+
+/** Captures [LayersTrace] from SurfaceFlinger. */
+open class LayersTraceMonitor @JvmOverloads constructor(private val traceFlags: Int = TRACE_FLAGS) :
+    TraceMonitor() {
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+    override val traceType = TraceType.SF
+    override val isEnabled
+        get() = windowManager.isLayerTracing
+
+    override fun start() {
+        windowManager.setLayerTracingFlags(traceFlags)
+        windowManager.isLayerTracing = true
+    }
+
+    override fun doStop(): File {
+        windowManager.isLayerTracing = false
+        return TRACE_DIR.resolve("layers_trace$WINSCOPE_EXT")
+    }
+
+    companion object {
+        const val TRACE_FLAGS = 0x47 // TRACE_CRITICAL|TRACE_INPUT|TRACE_COMPOSITION|TRACE_SYNC
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/TransactionsTraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/TransactionsTraceMonitor.kt
new file mode 100644
index 0000000..e16c7e0
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/surfaceflinger/TransactionsTraceMonitor.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.device.traces.monitors.surfaceflinger
+
+import android.tools.common.io.TraceType
+import android.tools.common.io.WINSCOPE_EXT
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.device.traces.monitors.TraceMonitor
+import android.view.WindowManagerGlobal
+import java.io.File
+
+/** Captures [TransactionsTrace] from SurfaceFlinger. */
+open class TransactionsTraceMonitor : TraceMonitor() {
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+    override val isEnabled = true
+    override val traceType = TraceType.TRANSACTION
+
+    override fun start() {
+        windowManager.setActiveTransactionTracing(true)
+    }
+
+    override fun doStop(): File {
+        windowManager.setActiveTransactionTracing(false)
+        return TRACE_DIR.resolve("transactions_trace$WINSCOPE_EXT")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/wm/TransitionsTraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/wm/TransitionsTraceMonitor.kt
new file mode 100644
index 0000000..7e3aa8d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/wm/TransitionsTraceMonitor.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.device.traces.monitors.wm
+
+import android.tools.common.io.TraceType
+import android.tools.common.io.WINSCOPE_EXT
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.device.traces.monitors.TraceMonitor
+import android.view.WindowManagerGlobal
+import java.io.File
+
+/** Captures [TransitionsTrace] from SurfaceFlinger. */
+open class TransitionsTraceMonitor : TraceMonitor() {
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+    override val traceType = TraceType.TRANSITION
+    override val isEnabled
+        get() = windowManager.isTransitionTraceEnabled
+
+    override fun start() {
+        windowManager.startTransitionTrace()
+    }
+
+    override fun doStop(): File {
+        windowManager.stopTransitionTrace()
+        return TRACE_DIR.resolve("transition_trace$WINSCOPE_EXT")
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitor.kt b/libraries/flicker/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitor.kt
new file mode 100644
index 0000000..65d701b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitor.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.device.traces.monitors.wm
+
+import android.tools.common.io.TraceType
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.monitors.TraceMonitor
+import android.view.WindowManagerGlobal
+import java.io.File
+
+/** Captures [WindowManagerTrace] from WindowManager. */
+open class WindowManagerTraceMonitor : TraceMonitor() {
+    private val windowManager = WindowManagerGlobal.getWindowManagerService()
+    override val traceType = TraceType.WM
+    override val isEnabled
+        get() = windowManager.isWindowTraceEnabled
+
+    override fun start() {
+        windowManager.startWindowTrace()
+    }
+
+    override fun doStop(): File {
+        windowManager.stopWindowTrace()
+        return TRACE_DIR.resolve(TraceType.WM.fileName)
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/DeviceDumpParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/DeviceDumpParser.kt
new file mode 100644
index 0000000..90938d7
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/DeviceDumpParser.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.device.traces.parsers
+
+import android.tools.common.CrossPlatform
+import android.tools.common.traces.DeviceStateDump
+import android.tools.common.traces.DeviceTraceDump
+import android.tools.common.traces.NullableDeviceStateDump
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
+import android.tools.device.traces.parsers.wm.WindowManagerDumpParser
+import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
+
+/**
+ * Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed and
+ * in raw (byte) data.
+ */
+class DeviceDumpParser {
+    companion object {
+        /**
+         * Creates a device state dump containing the [WindowManagerTrace] and [LayersTrace]
+         * obtained from a `dumpsys` command. The parsed traces will contain a single
+         * [WindowManagerState] or [LayerTraceEntry].
+         *
+         * @param wmTraceData [WindowManagerTrace] content
+         * @param layersTraceData [LayersTrace] content
+         * @param clearCacheAfterParsing If the caching used while parsing the proto should be
+         * ```
+         *                               cleared or remain in memory
+         * ```
+         */
+        @JvmStatic
+        fun fromNullableDump(
+            wmTraceData: ByteArray,
+            layersTraceData: ByteArray,
+            clearCacheAfterParsing: Boolean
+        ): NullableDeviceStateDump {
+            return CrossPlatform.log.withTracing("fromNullableDump") {
+                NullableDeviceStateDump(
+                    wmState =
+                        if (wmTraceData.isNotEmpty()) {
+                            WindowManagerDumpParser()
+                                .parse(wmTraceData, clearCache = clearCacheAfterParsing)
+                                .entries
+                                .first()
+                        } else {
+                            null
+                        },
+                    layerState =
+                        if (layersTraceData.isNotEmpty()) {
+                            LayersTraceParser()
+                                .parse(layersTraceData, clearCache = clearCacheAfterParsing)
+                                .entries
+                                .first()
+                        } else {
+                            null
+                        }
+                )
+            }
+        }
+
+        /** See [fromNullableDump] */
+        @JvmStatic
+        fun fromDump(
+            wmTraceData: ByteArray,
+            layersTraceData: ByteArray,
+            clearCacheAfterParsing: Boolean
+        ): DeviceStateDump {
+            return CrossPlatform.log.withTracing("fromDump") {
+                val nullableDump =
+                    fromNullableDump(wmTraceData, layersTraceData, clearCacheAfterParsing)
+                DeviceStateDump(
+                    nullableDump.wmState ?: error("WMState dump missing"),
+                    nullableDump.layerState ?: error("Layer State dump missing")
+                )
+            }
+        }
+
+        /**
+         * Creates a device state dump containing the WindowManager and Layers trace obtained from a
+         * regular trace. The parsed traces may contain a multiple [WindowManagerState] or
+         * [LayerTraceEntry].
+         *
+         * @param wmTraceData [WindowManagerTrace] content
+         * @param layersTraceData [LayersTrace] content
+         * @param clearCache If the caching used while parsing the proto should be
+         * ```
+         *                               cleared or remain in memory
+         * ```
+         */
+        @JvmStatic
+        fun fromTrace(
+            wmTraceData: ByteArray,
+            layersTraceData: ByteArray,
+            clearCache: Boolean
+        ): DeviceTraceDump {
+            return CrossPlatform.log.withTracing("fromTrace") {
+                DeviceTraceDump(
+                    wmTrace =
+                        if (wmTraceData.isNotEmpty()) {
+                            WindowManagerTraceParser().parse(wmTraceData, clearCache = clearCache)
+                        } else {
+                            null
+                        },
+                    layersTrace =
+                        if (layersTraceData.isNotEmpty()) {
+                            LayersTraceParser().parse(layersTraceData, clearCache = clearCache)
+                        } else {
+                            null
+                        }
+                )
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/Extensions.kt b/libraries/flicker/src/android/tools/device/traces/parsers/Extensions.kt
new file mode 100644
index 0000000..e99ec19
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/Extensions.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("Extensions")
+
+package android.tools.device.traces.parsers
+
+import android.content.ComponentName
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+fun Rect.toAndroidRect(): android.graphics.Rect {
+    return android.graphics.Rect(left, top, right, bottom)
+}
+
+fun android.graphics.Rect.toFlickerRect(): Rect {
+    return Rect.from(left, top, right, bottom)
+}
+
+/** Converts an Android [ComponentName] into a flicker [ComponentNameMatcher] */
+fun ComponentName.toFlickerComponent(): ComponentNameMatcher =
+    ComponentNameMatcher(this.packageName, this.className)
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/WaitForValidActivityState.kt b/libraries/flicker/src/android/tools/device/traces/parsers/WaitForValidActivityState.kt
new file mode 100644
index 0000000..835baf9
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/WaitForValidActivityState.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.device.traces.parsers
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration
+import android.tools.common.datatypes.component.IComponentMatcher
+
+data class WaitForValidActivityState(
+    @JvmField val activityMatcher: IComponentMatcher?,
+    @JvmField val windowIdentifier: String?,
+    @JvmField val stackId: Int,
+    @JvmField val windowingMode: Int,
+    @JvmField val activityType: Int
+) {
+    constructor(
+        activityName: IComponentMatcher
+    ) : this(
+        activityName,
+        windowIdentifier = activityName.toWindowIdentifier(),
+        stackId = ActivityTaskManager.INVALID_STACK_ID,
+        windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED,
+        activityType = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+    )
+
+    private constructor(
+        builder: Builder
+    ) : this(
+        activityMatcher = builder.activityName,
+        windowIdentifier = builder.windowIdentifier,
+        stackId = builder.stackId,
+        windowingMode = builder.windowingMode,
+        activityType = builder.activityType
+    )
+
+    override fun toString(): String {
+        val sb = StringBuilder("wait:")
+        if (activityMatcher != null) {
+            sb.append(" activity=").append(activityMatcher.toActivityIdentifier())
+        }
+        if (activityType != WindowConfiguration.ACTIVITY_TYPE_UNDEFINED) {
+            sb.append(" type=").append(activityTypeName(activityType))
+        }
+        if (windowIdentifier != null) {
+            sb.append(" window=").append(windowIdentifier)
+        }
+        if (windowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+            sb.append(" mode=").append(windowingModeName(windowingMode))
+        }
+        if (stackId != ActivityTaskManager.INVALID_STACK_ID) {
+            sb.append(" stack=").append(stackId)
+        }
+        return sb.toString()
+    }
+
+    class Builder constructor(internal var activityName: IComponentMatcher? = null) {
+        internal var windowIdentifier: String? = activityName?.toWindowIdentifier()
+        internal var stackId = ActivityTaskManager.INVALID_STACK_ID
+        internal var windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED
+        internal var activityType = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+
+        fun setWindowIdentifier(windowIdentifier: String): Builder {
+            this.windowIdentifier = windowIdentifier
+            return this
+        }
+
+        fun setStackId(stackId: Int): Builder {
+            this.stackId = stackId
+            return this
+        }
+
+        fun setWindowingMode(windowingMode: Int): Builder {
+            this.windowingMode = windowingMode
+            return this
+        }
+
+        fun setActivityType(activityType: Int): Builder {
+            this.activityType = activityType
+            return this
+        }
+
+        fun build(): WaitForValidActivityState {
+            return WaitForValidActivityState(this)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun forWindow(windowName: String): WaitForValidActivityState {
+            return Builder().setWindowIdentifier(windowName).build()
+        }
+
+        private fun windowingModeName(windowingMode: Int): String {
+            return when (windowingMode) {
+                WindowConfiguration.WINDOWING_MODE_UNDEFINED -> "UNDEFINED"
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN -> "FULLSCREEN"
+                WindowConfiguration.WINDOWING_MODE_PINNED -> "PINNED"
+                WindowConfiguration.WINDOWING_MODE_FREEFORM -> "FREEFORM"
+                else -> throw IllegalArgumentException("Unknown WINDOWING_MODE_: $windowingMode")
+            }
+        }
+
+        private fun activityTypeName(activityType: Int): String {
+            return when (activityType) {
+                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED -> "UNDEFINED"
+                WindowConfiguration.ACTIVITY_TYPE_STANDARD -> "STANDARD"
+                WindowConfiguration.ACTIVITY_TYPE_HOME -> "HOME"
+                WindowConfiguration.ACTIVITY_TYPE_RECENTS -> "RECENTS"
+                WindowConfiguration.ACTIVITY_TYPE_ASSISTANT -> "ASSISTANT"
+                else -> throw IllegalArgumentException("Unknown ACTIVITY_TYPE_: $activityType")
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/WindowManagerStateHelper.kt b/libraries/flicker/src/android/tools/device/traces/parsers/WindowManagerStateHelper.kt
new file mode 100644
index 0000000..a34989d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/WindowManagerStateHelper.kt
@@ -0,0 +1,477 @@
+/*
+ * 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.device.traces.parsers
+
+import android.app.ActivityTaskManager
+import android.app.Instrumentation
+import android.app.WindowConfiguration
+import android.os.SystemClock
+import android.os.Trace
+import android.tools.common.CrossPlatform
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.IME
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SNAPSHOT
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SPLASH_SCREEN
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SPLIT_DIVIDER
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.traces.Condition
+import android.tools.common.traces.ConditionsFactory
+import android.tools.common.traces.DeviceStateDump
+import android.tools.common.traces.WaitCondition
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.ConfigurationContainer
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.common.traces.wm.WindowState
+import android.tools.device.traces.LOG_TAG
+import android.tools.device.traces.getCurrentStateDump
+import android.view.Display
+import androidx.test.platform.app.InstrumentationRegistry
+
+/** Helper class to wait on [WindowManagerState] or [LayerTraceEntry] conditions */
+open class WindowManagerStateHelper
+@JvmOverloads
+constructor(
+    /** Instrumentation to run the tests */
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    private val clearCacheAfterParsing: Boolean = true,
+    /** Predicate to supply a new UI information */
+    private val deviceDumpSupplier: () -> DeviceStateDump = {
+        getCurrentStateDump(clearCacheAfterParsing = clearCacheAfterParsing)
+    },
+    /** Number of attempts to satisfy a wait condition */
+    private val numRetries: Int = WaitCondition.DEFAULT_RETRY_LIMIT,
+    /** Interval between wait for state dumps during wait conditions */
+    private val retryIntervalMs: Long = WaitCondition.DEFAULT_RETRY_INTERVAL_MS
+) {
+    private var internalState: DeviceStateDump? = null
+
+    /** Queries the supplier for a new device state */
+    val currentState: DeviceStateDump
+        get() {
+            if (internalState == null) {
+                internalState = deviceDumpSupplier.invoke()
+            } else {
+                StateSyncBuilder().withValidState().waitFor()
+            }
+            return internalState ?: error("Unable to fetch an internal state")
+        }
+
+    protected open fun updateCurrState(value: DeviceStateDump) {
+        internalState = value
+    }
+
+    /**
+     * @return a [WindowState] from the current device state matching [componentMatcher], or null
+     * otherwise
+     *
+     * @param componentMatcher Components to search
+     */
+    fun getWindow(componentMatcher: IComponentMatcher): WindowState? {
+        return this.currentState.wmState.windowStates.firstOrNull {
+            componentMatcher.windowMatchesAnyOf(it)
+        }
+    }
+
+    /**
+     * @return The frame [Region] a [WindowState] matching [componentMatcher]
+     *
+     * @param componentMatcher Components to search
+     */
+    fun getWindowRegion(componentMatcher: IComponentMatcher): Region =
+        getWindow(componentMatcher)?.frameRegion ?: Region.EMPTY
+
+    /**
+     * Class to build conditions for waiting on specific [WindowManagerTrace] and [LayersTrace]
+     * conditions
+     */
+    inner class StateSyncBuilder {
+        private val conditionBuilder = createConditionBuilder()
+        private var lastMessage = ""
+
+        private fun createConditionBuilder(): WaitCondition.Builder<DeviceStateDump> =
+            WaitCondition.Builder(deviceDumpSupplier, numRetries)
+                .onStart { Trace.beginSection(it) }
+                .onEnd { Trace.endSection() }
+                .onSuccess { updateCurrState(it) }
+                .onFailure { updateCurrState(it) }
+                .onLog { msg, isError ->
+                    lastMessage = msg
+                    if (isError) {
+                        CrossPlatform.log.e(LOG_TAG, msg)
+                    } else {
+                        CrossPlatform.log.d(LOG_TAG, msg)
+                    }
+                }
+                .onRetry { SystemClock.sleep(retryIntervalMs) }
+
+        /**
+         * Adds a new [condition] to the list
+         *
+         * @param condition to wait for
+         */
+        fun add(condition: Condition<DeviceStateDump>): StateSyncBuilder = apply {
+            conditionBuilder.withCondition(condition)
+        }
+
+        /**
+         * Adds a new [condition] to the list
+         *
+         * @param message describing the condition
+         * @param condition to wait for
+         */
+        @JvmOverloads
+        fun add(message: String = "", condition: (DeviceStateDump) -> Boolean): StateSyncBuilder =
+            add(Condition(message, condition))
+
+        /**
+         * Waits until the list of conditions added to [conditionBuilder] are satisfied
+         *
+         * @return if the device state passed all conditions or not
+         */
+        fun waitFor(): Boolean {
+            val passed = conditionBuilder.build().waitFor()
+            // Ensure WindowManagerService wait until all animations have completed
+            instrumentation.waitForIdleSync()
+            instrumentation.uiAutomation.syncInputTransactions()
+            return passed
+        }
+
+        /**
+         * Waits until the list of conditions added to [conditionBuilder] are satisfied and verifies
+         * the device state passes all conditions
+         *
+         * @throws IllegalArgumentException if the conditions were not met
+         */
+        fun waitForAndVerify() {
+            val success = waitFor()
+            require(success) { lastMessage }
+        }
+
+        /**
+         * Waits for an app matching [componentMatcher] to be visible, in full screen, and for
+         * nothing to be animating
+         *
+         * @param componentMatcher Components to search
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withFullScreenApp(
+            componentMatcher: IComponentMatcher,
+            displayId: Int = Display.DEFAULT_DISPLAY
+        ) =
+            withFullScreenAppCondition(componentMatcher)
+                .withAppTransitionIdle(displayId)
+                .add(ConditionsFactory.isLayerVisible(componentMatcher))
+
+        /**
+         * Waits until the home activity is visible and nothing to be animating
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withHomeActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .add(ConditionsFactory.isHomeActivityVisible())
+                .add(ConditionsFactory.isLauncherLayerVisible())
+
+        /**
+         * Waits until the split-screen divider is visible and nothing to be animating
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withSplitDividerVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.isLayerVisible(SPLIT_DIVIDER))
+
+        /**
+         * Waits until the home activity is visible and nothing to be animating
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withRecentsActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId)
+                .add(ConditionsFactory.isRecentsActivityVisible())
+                .add(ConditionsFactory.isLayerVisible(LAUNCHER))
+
+        /**
+         * Wait for specific rotation for the display with id [displayId]
+         *
+         * @param rotation expected. Values are [Surface#Rotation]
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withRotation(rotation: Rotation, displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.hasRotation(rotation, displayId))
+
+        /**
+         * Waits until a [WindowState] matching [componentMatcher] has a state of [activityState]
+         *
+         * @param componentMatcher Components to search
+         * @param activityState expected activity state
+         */
+        fun withActivityState(componentMatcher: IComponentMatcher, activityState: String) =
+            add(
+                Condition(
+                    "state of ${componentMatcher.toActivityIdentifier()} to be $activityState"
+                ) { it.wmState.hasActivityState(componentMatcher, activityState) }
+            )
+
+        /**
+         * Waits until the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] are
+         * visible (windows and layers)
+         */
+        fun withNavOrTaskBarVisible() = add(ConditionsFactory.isNavOrTaskBarVisible())
+
+        /** Waits until the navigation and status bars are visible (windows and layers) */
+        fun withStatusBarVisible() = add(ConditionsFactory.isStatusBarVisible())
+
+        /**
+         * Wait until neither an [Activity] nor a [WindowState] matching [componentMatcher] exist on
+         * the display with id [displayId] and for nothing to be animating
+         *
+         * @param componentMatcher Components to search
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withActivityRemoved(
+            componentMatcher: IComponentMatcher,
+            displayId: Int = Display.DEFAULT_DISPLAY
+        ) =
+            withAppTransitionIdle(displayId)
+                .add(ConditionsFactory.containsActivity(componentMatcher).negate())
+                .add(ConditionsFactory.containsWindow(componentMatcher).negate())
+
+        /**
+         * Wait until the splash screen and snapshot starting windows no longer exist, no layers are
+         * animating, and [WindowManagerState] is idle on display [displayId]
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withAppTransitionIdle(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withSplashScreenGone()
+                .withSnapshotGone()
+                .add(ConditionsFactory.isAppTransitionIdle(displayId))
+                .add(ConditionsFactory.hasLayersAnimating().negate())
+
+        /**
+         * Wait until least one [WindowState] matching [componentMatcher] is not visible on display
+         * with idd [displayId] and nothing is animating
+         *
+         * @param componentMatcher Components to search
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withWindowSurfaceDisappeared(
+            componentMatcher: IComponentMatcher,
+            displayId: Int = Display.DEFAULT_DISPLAY
+        ) =
+            withAppTransitionIdle(displayId)
+                .add(ConditionsFactory.isWindowSurfaceShown(componentMatcher).negate())
+                .add(ConditionsFactory.isLayerVisible(componentMatcher).negate())
+                .add(ConditionsFactory.isAppTransitionIdle(displayId))
+
+        /**
+         * Wait until least one [WindowState] matching [componentMatcher] is visible on display with
+         * idd [displayId] and nothing is animating
+         *
+         * @param componentMatcher Components to search
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withWindowSurfaceAppeared(
+            componentMatcher: IComponentMatcher,
+            displayId: Int = Display.DEFAULT_DISPLAY
+        ) =
+            withAppTransitionIdle(displayId)
+                .add(ConditionsFactory.isWindowSurfaceShown(componentMatcher))
+                .add(ConditionsFactory.isLayerVisible(componentMatcher))
+
+        /**
+         * Waits until the IME window and layer are visible
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withImeShown(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.isImeShown(displayId))
+
+        /**
+         * Waits until the [IME] layer is no longer visible.
+         *
+         * Cannot wait for the window as its visibility information is updated at a later state and
+         * is not reliable in the trace
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withImeGone(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.isLayerVisible(IME).negate())
+
+        /**
+         * Waits until a window is in PIP mode. That is:
+         *
+         * - wait until a window is pinned ([WindowManagerState.pinnedWindows])
+         * - no layers animating
+         * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withPipShown(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.hasPipWindow())
+
+        /**
+         * Waits until a window is no longer in PIP mode. That is:
+         *
+         * - wait until there are no pinned ([WindowManagerState.pinnedWindows])
+         * - no layers animating
+         * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
+         *
+         * @param displayId of the target display
+         */
+        @JvmOverloads
+        fun withPipGone(displayId: Int = Display.DEFAULT_DISPLAY) =
+            withAppTransitionIdle(displayId).add(ConditionsFactory.hasPipWindow().negate())
+
+        /** Waits until the [SNAPSHOT] is gone */
+        fun withSnapshotGone() = add(ConditionsFactory.isLayerVisible(SNAPSHOT).negate())
+
+        /** Waits until the [SPLASH_SCREEN] is gone */
+        fun withSplashScreenGone() = add(ConditionsFactory.isLayerVisible(SPLASH_SCREEN).negate())
+
+        /** Waits until the is no top visible app window in the [WindowManagerState] */
+        fun withoutTopVisibleAppWindows() =
+            add("noAppWindowsOnTop") { it.wmState.topVisibleAppWindow == null }
+
+        /** Waits until the keyguard is showing */
+        fun withKeyguardShowing() = add("withKeyguardShowing") { it.wmState.isKeyguardShowing }
+
+        /**
+         * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
+         *
+         * @param waitForActivityState array of activity states to wait for.
+         */
+        internal fun withValidState(vararg waitForActivityState: WaitForValidActivityState) =
+            waitForValidStateCondition(*waitForActivityState)
+
+        private fun waitForValidStateCondition(vararg waitForCondition: WaitForValidActivityState) =
+            apply {
+                add(ConditionsFactory.isWMStateComplete())
+                if (waitForCondition.isNotEmpty()) {
+                    add(
+                        Condition("!shouldWaitForActivities") {
+                            !shouldWaitForActivities(it, *waitForCondition)
+                        }
+                    )
+                }
+            }
+
+        fun withFullScreenAppCondition(componentMatcher: IComponentMatcher) =
+            waitForValidStateCondition(
+                WaitForValidActivityState.Builder(componentMatcher)
+                    .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+                    .setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+                    .build()
+            )
+    }
+
+    companion object {
+        /** @return true if it should wait for some activities to become visible. */
+        private fun shouldWaitForActivities(
+            state: DeviceStateDump,
+            vararg waitForActivitiesVisible: WaitForValidActivityState
+        ): Boolean {
+            if (waitForActivitiesVisible.isEmpty()) {
+                return false
+            }
+            // If the caller is interested in waiting for some particular activity windows to be
+            // visible before compute the state. Check for the visibility of those activity windows
+            // and for placing them in correct stacks (if requested).
+            var allActivityWindowsVisible = true
+            var tasksInCorrectStacks = true
+            for (activityState in waitForActivitiesVisible) {
+                val matchingWindowStates =
+                    state.wmState.getMatchingVisibleWindowState(
+                        activityState.activityMatcher
+                            ?: error("Activity name missing in $activityState")
+                    )
+                val activityWindowVisible = matchingWindowStates.isNotEmpty()
+
+                if (!activityWindowVisible) {
+                    CrossPlatform.log.i(
+                        LOG_TAG,
+                        "Activity window not visible: ${activityState.windowIdentifier}"
+                    )
+                    allActivityWindowsVisible = false
+                } else if (!state.wmState.isActivityVisible(activityState.activityMatcher)) {
+                    CrossPlatform.log.i(
+                        LOG_TAG,
+                        "Activity not visible: ${activityState.activityMatcher}"
+                    )
+                    allActivityWindowsVisible = false
+                } else {
+                    // Check if window is already the correct state requested by test.
+                    var windowInCorrectState = false
+                    for (ws in matchingWindowStates) {
+                        if (
+                            activityState.stackId != ActivityTaskManager.INVALID_STACK_ID &&
+                                ws.stackId != activityState.stackId
+                        ) {
+                            continue
+                        }
+                        if (!ws.isWindowingModeCompatible(activityState.windowingMode)) {
+                            continue
+                        }
+                        if (
+                            activityState.activityType !=
+                                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED &&
+                                ws.activityType != activityState.activityType
+                        ) {
+                            continue
+                        }
+                        windowInCorrectState = true
+                        break
+                    }
+                    if (!windowInCorrectState) {
+                        CrossPlatform.log.i(LOG_TAG, "Window in incorrect stack: $activityState")
+                        tasksInCorrectStacks = false
+                    }
+                }
+            }
+            return !allActivityWindowsVisible || !tasksInCorrectStacks
+        }
+
+        private fun ConfigurationContainer.isWindowingModeCompatible(
+            requestedWindowingMode: Int
+        ): Boolean {
+            return when (requestedWindowingMode) {
+                WindowConfiguration.WINDOWING_MODE_UNDEFINED -> true
+                else -> windowingMode == requestedWindowingMode
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/LayersTraceParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/LayersTraceParser.kt
new file mode 100644
index 0000000..b33702b
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/LayersTraceParser.kt
@@ -0,0 +1,254 @@
+/*
+ * 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.device.traces.parsers.surfaceflinger
+
+import android.surfaceflinger.Common
+import android.surfaceflinger.Display
+import android.surfaceflinger.Layers
+import android.surfaceflinger.Layerstrace
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Matrix33
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.Size
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.common.traces.surfaceflinger.HwcCompositionType
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayerTraceEntryBuilder
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.Transform
+import android.tools.common.traces.surfaceflinger.Transform.Companion.isFlagClear
+import android.tools.common.traces.surfaceflinger.Transform.Companion.isFlagSet
+
+/** Parser for [LayersTrace] objects containing traces or state dumps */
+class LayersTraceParser(
+    private val ignoreLayersStackMatchNoDisplay: Boolean = true,
+    private val ignoreLayersInVirtualDisplay: Boolean = true,
+    private val legacyTrace: Boolean = false,
+    private val orphanLayerCallback: ((Layer) -> Boolean)? = null,
+) :
+    AbstractTraceParser<
+        Layerstrace.LayersTraceFileProto, Layerstrace.LayersTraceProto, LayerTraceEntry, LayersTrace
+    >() {
+    private var realToElapsedTimeOffsetNanos = 0L
+
+    override val traceName: String = "Layers Trace"
+
+    override fun doDecodeByteArray(bytes: ByteArray): Layerstrace.LayersTraceFileProto =
+        Layerstrace.LayersTraceFileProto.parseFrom(bytes)
+
+    override fun createTrace(entries: List<LayerTraceEntry>): LayersTrace =
+        LayersTrace(entries.toTypedArray())
+
+    override fun getEntries(
+        input: Layerstrace.LayersTraceFileProto
+    ): List<Layerstrace.LayersTraceProto> = input.entryList
+
+    override fun getTimestamp(entry: Layerstrace.LayersTraceProto): Timestamp {
+        require(legacyTrace || realToElapsedTimeOffsetNanos != 0L)
+        return CrossPlatform.timestamp.from(
+            systemUptimeNanos = entry.elapsedRealtimeNanos,
+            unixNanos = entry.elapsedRealtimeNanos + realToElapsedTimeOffsetNanos
+        )
+    }
+
+    override fun onBeforeParse(input: Layerstrace.LayersTraceFileProto) {
+        realToElapsedTimeOffsetNanos = input.realToElapsedTimeOffsetNanos
+    }
+
+    override fun doParseEntry(entry: Layerstrace.LayersTraceProto): LayerTraceEntry {
+        val layers = entry.layers.layersList.map { newLayer(it) }.toTypedArray()
+        val displays = entry.displaysList.map { newDisplay(it) }.toTypedArray()
+        val builder =
+            LayerTraceEntryBuilder()
+                .setElapsedTimestamp(entry.elapsedRealtimeNanos.toString())
+                .setLayers(layers)
+                .setDisplays(displays)
+                .setVSyncId(entry.vsyncId.toString())
+                .setHwcBlob(entry.hwcBlob)
+                .setWhere(entry.where)
+                .setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNanos.toString())
+                .setOrphanLayerCallback(orphanLayerCallback)
+                .ignoreLayersStackMatchNoDisplay(ignoreLayersStackMatchNoDisplay)
+                .ignoreVirtualDisplay(ignoreLayersInVirtualDisplay)
+        return builder.build()
+    }
+
+    companion object {
+        private fun newLayer(
+            proto: Layers.LayerProto,
+            excludeCompositionState: Boolean = false
+        ): Layer {
+            // Differentiate between the cases when there's no HWC data on
+            // the trace, and when the visible region is actually empty
+            val activeBuffer = proto.activeBuffer.toBuffer()
+            val visibleRegion = proto.visibleRegion.toRegion() ?: Region.EMPTY
+            val crop = proto.crop?.toCropRect()
+            return Layer.from(
+                proto.name ?: "",
+                proto.id,
+                proto.parent,
+                proto.z,
+                visibleRegion,
+                activeBuffer,
+                proto.flags,
+                proto.bounds?.toRectF() ?: RectF.EMPTY,
+                proto.color.toColor(),
+                proto.isOpaque,
+                proto.shadowRadius,
+                proto.cornerRadius,
+                proto.type ?: "",
+                proto.screenBounds?.toRectF() ?: RectF.EMPTY,
+                createTransformFromProto(proto.transform, proto.position),
+                proto.sourceBounds?.toRectF() ?: RectF.EMPTY,
+                proto.currFrame,
+                proto.effectiveScalingMode,
+                createTransformFromProto(proto.bufferTransform, position = null),
+                toHwcCompositionType(proto.hwcCompositionType),
+                proto.hwcCrop.toRectF() ?: RectF.EMPTY,
+                proto.hwcFrame.toRect(),
+                proto.backgroundBlurRadius,
+                crop,
+                proto.isRelativeOf,
+                proto.zOrderRelativeOf,
+                proto.layerStack,
+                createTransformFromProto(proto.transform, position = proto.requestedPosition),
+                proto.requestedColor.toColor(),
+                proto.cornerRadiusCrop?.toRectF() ?: RectF.EMPTY,
+                createTransformFromProto(proto.inputWindowInfo?.transform, position = null),
+                proto.inputWindowInfo?.touchableRegion?.toRegion(),
+                excludeCompositionState
+            )
+        }
+
+        private fun newDisplay(
+            proto: Display.DisplayProto
+        ): android.tools.common.traces.surfaceflinger.Display {
+            return android.tools.common.traces.surfaceflinger.Display.from(
+                proto.id.toULong(),
+                proto.name,
+                proto.layerStack,
+                proto.size.toSize(),
+                proto.layerStackSpaceRect.toRect(),
+                createTransformFromProto(proto.transform, position = null),
+                proto.isVirtual
+            )
+        }
+
+        private fun Layers.FloatRectProto?.toRectF(): RectF? {
+            return this?.let { RectF.from(left = left, top = top, right = right, bottom = bottom) }
+        }
+
+        private fun Common.SizeProto?.toSize(): Size {
+            return this?.let { Size.from(this.w, this.h) } ?: Size.EMPTY
+        }
+
+        private fun Common.ColorProto?.toColor(): Color {
+            return this?.let { Color.from(r, g, b, a) } ?: Color.EMPTY
+        }
+
+        private fun Layers.ActiveBufferProto?.toBuffer(): ActiveBuffer {
+            return this?.let { ActiveBuffer.from(width, height, stride, format) }
+                ?: ActiveBuffer.EMPTY
+        }
+
+        private fun toHwcCompositionType(value: Layers.HwcCompositionType): HwcCompositionType {
+            return when (value) {
+                Layers.HwcCompositionType.INVALID -> HwcCompositionType.INVALID
+                Layers.HwcCompositionType.CLIENT -> HwcCompositionType.CLIENT
+                Layers.HwcCompositionType.DEVICE -> HwcCompositionType.DEVICE
+                Layers.HwcCompositionType.SOLID_COLOR -> HwcCompositionType.SOLID_COLOR
+                Layers.HwcCompositionType.CURSOR -> HwcCompositionType.CURSOR
+                Layers.HwcCompositionType.SIDEBAND -> HwcCompositionType.SIDEBAND
+                else -> HwcCompositionType.UNRECOGNIZED
+            }
+        }
+
+        private fun Common.RectProto?.toCropRect(): Rect? {
+            return when {
+                this == null -> Rect.EMPTY
+                // crop (0,0) (-1,-1) means no crop
+                right == -1 && left == 0 && bottom == -1 && top == 0 -> null
+                (right - left) <= 0 || (bottom - top) <= 0 -> Rect.EMPTY
+                else -> Rect.from(left, top, right, bottom)
+            }
+        }
+
+        /**
+         * Extracts [Rect] from [Common.RegionProto] by returning a rect that encompasses all the
+         * rectangles making up the region.
+         */
+        private fun Common.RegionProto?.toRegion(): Region? {
+            return this?.let {
+                val rectArray = this.rectList.map { it.toRect() }.toTypedArray()
+                return Region(rectArray)
+            }
+        }
+
+        private fun Common.RectProto?.toRect(): Rect =
+            Rect.from(this?.left ?: 0, this?.top ?: 0, this?.right ?: 0, this?.bottom ?: 0)
+
+        fun createTransformFromProto(
+            transform: Common.TransformProto?,
+            position: Layers.PositionProto?
+        ) = Transform.from(transform?.type, getMatrix(transform, position))
+
+        private fun getMatrix(
+            transform: Common.TransformProto?,
+            position: Layers.PositionProto?
+        ): Matrix33 {
+            val x = position?.x ?: 0f
+            val y = position?.y ?: 0f
+
+            return when {
+                transform == null || Transform.isSimpleTransform(transform.type) ->
+                    transform?.type.getDefaultTransform(x, y)
+                else ->
+                    Matrix33.from(
+                        transform.dsdx,
+                        transform.dtdx,
+                        x,
+                        transform.dsdy,
+                        transform.dtdy,
+                        y
+                    )
+            }
+        }
+
+        private fun Int?.getDefaultTransform(x: Float, y: Float): Matrix33 {
+            return when {
+                // IDENTITY
+                this == null -> Matrix33.identity(x, y)
+                // // ROT_270 = ROT_90|FLIP_H|FLIP_V
+                isFlagSet(Transform.ROT_90_VAL or Transform.FLIP_V_VAL or Transform.FLIP_H_VAL) ->
+                    Matrix33.rot270(x, y)
+                // ROT_180 = FLIP_H|FLIP_V
+                isFlagSet(Transform.FLIP_V_VAL or Transform.FLIP_H_VAL) -> Matrix33.rot180(x, y)
+                // ROT_90
+                isFlagSet(Transform.ROT_90_VAL) -> Matrix33.rot90(x, y)
+                // IDENTITY
+                isFlagClear(Transform.SCALE_VAL or Transform.ROTATE_VAL) -> Matrix33.identity(x, y)
+                else -> throw IllegalStateException("Unknown transform type $this")
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/TransactionsTraceParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/TransactionsTraceParser.kt
new file mode 100644
index 0000000..33c5d0e
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/surfaceflinger/TransactionsTraceParser.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.device.traces.parsers.surfaceflinger
+
+import android.surfaceflinger.proto.Transactions
+import android.surfaceflinger.proto.Transactions.TransactionState
+import android.surfaceflinger.proto.Transactions.TransactionTraceFile
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.common.traces.surfaceflinger.Transaction
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTraceEntry
+import android.tools.common.traces.wm.TransitionsTrace
+
+/** Parser for [TransitionsTrace] objects */
+class TransactionsTraceParser :
+    AbstractTraceParser<
+        TransactionTraceFile,
+        Transactions.TransactionTraceEntry,
+        TransactionsTraceEntry,
+        TransactionsTrace
+    >() {
+    private var timestampOffset = 0L
+    override val traceName: String = "Transactions trace"
+
+    override fun onBeforeParse(input: TransactionTraceFile) {
+        timestampOffset = input.realToElapsedTimeOffsetNanos
+    }
+
+    override fun getTimestamp(entry: Transactions.TransactionTraceEntry): Timestamp {
+        require(timestampOffset != 0L)
+        return CrossPlatform.timestamp.from(
+            elapsedNanos = entry.elapsedRealtimeNanos,
+            unixNanos = entry.elapsedRealtimeNanos + timestampOffset
+        )
+    }
+
+    override fun createTrace(entries: List<TransactionsTraceEntry>): TransactionsTrace =
+        TransactionsTrace(entries.toTypedArray())
+
+    override fun getEntries(input: TransactionTraceFile): List<Transactions.TransactionTraceEntry> =
+        input.entryList
+
+    override fun doDecodeByteArray(bytes: ByteArray): TransactionTraceFile =
+        TransactionTraceFile.parseFrom(bytes)
+
+    override fun doParseEntry(entry: Transactions.TransactionTraceEntry): TransactionsTraceEntry {
+        val transactions = parseTransactionsProto(entry.transactionsList)
+        val transactionsTraceEntry =
+            TransactionsTraceEntry(
+                CrossPlatform.timestamp.from(
+                    elapsedNanos = entry.elapsedRealtimeNanos,
+                    elapsedOffsetNanos = timestampOffset
+                ),
+                entry.vsyncId,
+                transactions
+            )
+        transactions.forEach { it.appliedInEntry = transactionsTraceEntry }
+        return transactionsTraceEntry
+    }
+
+    private fun parseTransactionsProto(
+        transactionStates: List<TransactionState>
+    ): Array<Transaction> {
+        val transactions = mutableListOf<Transaction>()
+        for (state in transactionStates) {
+            val transaction =
+                Transaction(
+                    state.pid,
+                    state.uid,
+                    state.vsyncId,
+                    state.postTime,
+                    state.transactionId
+                )
+            transactions.add(transaction)
+        }
+
+        return transactions.toTypedArray()
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/wm/TransitionsTraceParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/wm/TransitionsTraceParser.kt
new file mode 100644
index 0000000..724ac3d
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/wm/TransitionsTraceParser.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.common.traces.wm.Transition
+import android.tools.common.traces.wm.Transition.Companion.Type
+import android.tools.common.traces.wm.TransitionChange
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowingMode
+import com.android.server.wm.shell.nano.TransitionTraceProto
+
+/** Parser for [TransitionsTrace] objects */
+class TransitionsTraceParser :
+    AbstractTraceParser<
+        TransitionTraceProto,
+        com.android.server.wm.shell.nano.Transition,
+        Transition,
+        TransitionsTrace
+    >() {
+    override val traceName: String = "Transition trace"
+
+    override fun createTrace(entries: List<Transition>): TransitionsTrace {
+        return TransitionsTrace(entries.toTypedArray())
+    }
+
+    override fun doDecodeByteArray(bytes: ByteArray): TransitionTraceProto =
+        TransitionTraceProto.parseFrom(bytes)
+
+    override fun shouldParseEntry(entry: com.android.server.wm.shell.nano.Transition): Boolean {
+        return true
+    }
+
+    override fun getEntries(
+        input: TransitionTraceProto
+    ): List<com.android.server.wm.shell.nano.Transition> = input.sentTransitions.toList()
+
+    override fun getTimestamp(entry: com.android.server.wm.shell.nano.Transition): Timestamp {
+        return CrossPlatform.timestamp.from(elapsedNanos = entry.createTimeNs)
+    }
+
+    override fun onBeforeParse(input: TransitionTraceProto) {}
+
+    override fun doParseEntry(entry: com.android.server.wm.shell.nano.Transition): Transition {
+        val windowingMode = WindowingMode.WINDOWING_MODE_UNDEFINED // TODO: Get the windowing mode
+        val changes =
+            entry.targets.map {
+                TransitionChange(Type.fromInt(it.mode), it.layerId, it.windowId, windowingMode)
+            }
+
+        return Transition(
+            start = CrossPlatform.timestamp.from(elapsedNanos = entry.createTimeNs),
+            sendTime = CrossPlatform.timestamp.from(elapsedNanos = entry.sendTimeNs),
+            startTransactionId = entry.startTransactionId,
+            finishTransactionId = entry.finishTransactionId,
+            changes = changes,
+            played = true,
+            aborted = false,
+        )
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParser.kt
new file mode 100644
index 0000000..7936e07
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParser.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.tools.common.parsers.AbstractParser
+import android.tools.common.traces.wm.WindowManagerTrace
+import com.android.server.wm.nano.WindowManagerServiceDumpProto
+
+/** Parser for [WindowManagerTrace] objects containing dumps */
+class WindowManagerDumpParser :
+    AbstractParser<WindowManagerServiceDumpProto, WindowManagerTrace>() {
+    override val traceName: String = "WM Dump"
+
+    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerServiceDumpProto =
+        WindowManagerServiceDumpProto.parseFrom(bytes)
+
+    override fun doParse(input: WindowManagerServiceDumpProto): WindowManagerTrace {
+        val parsedEntry = WindowManagerStateBuilder().forProto(input).build()
+        return WindowManagerTrace(arrayOf(parsedEntry))
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerStateBuilder.kt b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerStateBuilder.kt
new file mode 100644
index 0000000..29ad7e8
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerStateBuilder.kt
@@ -0,0 +1,580 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.app.nano.WindowConfigurationProto
+import android.content.nano.ConfigurationProto
+import android.graphics.nano.RectProto
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Insets
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Size
+import android.tools.common.traces.wm.Activity
+import android.tools.common.traces.wm.Configuration
+import android.tools.common.traces.wm.ConfigurationContainer
+import android.tools.common.traces.wm.DisplayArea
+import android.tools.common.traces.wm.DisplayContent
+import android.tools.common.traces.wm.DisplayCutout
+import android.tools.common.traces.wm.KeyguardControllerState
+import android.tools.common.traces.wm.RootWindowContainer
+import android.tools.common.traces.wm.Task
+import android.tools.common.traces.wm.TaskFragment
+import android.tools.common.traces.wm.WindowConfiguration
+import android.tools.common.traces.wm.WindowContainer
+import android.tools.common.traces.wm.WindowLayoutParams
+import android.tools.common.traces.wm.WindowManagerPolicy
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTraceEntryBuilder
+import android.tools.common.traces.wm.WindowState
+import android.tools.common.traces.wm.WindowToken
+import android.view.Surface
+import android.view.nano.DisplayCutoutProto
+import android.view.nano.ViewProtoEnums
+import android.view.nano.WindowLayoutParamsProto
+import com.android.server.wm.nano.ActivityRecordProto
+import com.android.server.wm.nano.AppTransitionProto
+import com.android.server.wm.nano.ConfigurationContainerProto
+import com.android.server.wm.nano.DisplayAreaProto
+import com.android.server.wm.nano.DisplayContentProto
+import com.android.server.wm.nano.KeyguardControllerProto
+import com.android.server.wm.nano.RootWindowContainerProto
+import com.android.server.wm.nano.TaskFragmentProto
+import com.android.server.wm.nano.TaskProto
+import com.android.server.wm.nano.WindowContainerChildProto
+import com.android.server.wm.nano.WindowContainerProto
+import com.android.server.wm.nano.WindowManagerPolicyProto
+import com.android.server.wm.nano.WindowManagerServiceDumpProto
+import com.android.server.wm.nano.WindowStateProto
+import com.android.server.wm.nano.WindowTokenProto
+
+/** Helper class to create a new WM state */
+class WindowManagerStateBuilder {
+    private var computedZCounter = 0
+    private var realToElapsedTimeOffsetNanos = 0L
+    private var where = ""
+    private var timestamp = 0L
+    private var proto: WindowManagerServiceDumpProto? = null
+
+    fun withRealTimeOffset(value: Long) = apply { realToElapsedTimeOffsetNanos = value }
+
+    fun atPlace(_where: String) = apply { where = _where }
+
+    fun forTimestamp(value: Long) = apply { timestamp = value }
+
+    fun forProto(value: WindowManagerServiceDumpProto) = apply { proto = value }
+
+    fun build(): WindowManagerState {
+        val proto = proto
+        requireNotNull(proto) { "Proto object not specified" }
+
+        computedZCounter = 0
+        return WindowManagerTraceEntryBuilder(
+                _elapsedTimestamp = timestamp.toString(),
+                policy = createWindowManagerPolicy(proto.policy),
+                focusedApp = proto.focusedApp,
+                focusedDisplayId = proto.focusedDisplayId,
+                focusedWindow = proto.focusedWindow?.title ?: "",
+                inputMethodWindowAppToken =
+                    if (proto.inputMethodWindow != null) {
+                        Integer.toHexString(proto.inputMethodWindow.hashCode)
+                    } else {
+                        ""
+                    },
+                isHomeRecentsComponent = proto.rootWindowContainer.isHomeRecentsComponent,
+                isDisplayFrozen = proto.displayFrozen,
+                pendingActivities =
+                    proto.rootWindowContainer.pendingActivities.map { it.title }.toTypedArray(),
+                root = createRootWindowContainer(proto.rootWindowContainer),
+                keyguardControllerState =
+                    createKeyguardControllerState(proto.rootWindowContainer.keyguardController),
+                where = where,
+                realToElapsedTimeOffsetNs = realToElapsedTimeOffsetNanos.toString()
+            )
+            .build()
+    }
+
+    private fun createWindowManagerPolicy(proto: WindowManagerPolicyProto): WindowManagerPolicy {
+        return WindowManagerPolicy.from(
+            focusedAppToken = proto.focusedAppToken ?: "",
+            forceStatusBar = proto.forceStatusBar,
+            forceStatusBarFromKeyguard = proto.forceStatusBarFromKeyguard,
+            keyguardDrawComplete = proto.keyguardDrawComplete,
+            keyguardOccluded = proto.keyguardOccluded,
+            keyguardOccludedChanged = proto.keyguardOccludedChanged,
+            keyguardOccludedPending = proto.keyguardOccludedPending,
+            lastSystemUiFlags = proto.lastSystemUiFlags,
+            orientation = proto.orientation,
+            rotation = Rotation.getByValue(proto.rotation),
+            rotationMode = proto.rotationMode,
+            screenOnFully = proto.screenOnFully,
+            windowManagerDrawComplete = proto.windowManagerDrawComplete
+        )
+    }
+
+    private fun createRootWindowContainer(proto: RootWindowContainerProto): RootWindowContainer {
+        return RootWindowContainer(
+            createWindowContainer(
+                proto.windowContainer,
+                proto.windowContainer.children.mapNotNull { p ->
+                    createWindowContainerChild(p, isActivityInTree = false)
+                }
+            )
+                ?: error("Window container should not be null")
+        )
+    }
+
+    private fun createKeyguardControllerState(
+        proto: KeyguardControllerProto?
+    ): KeyguardControllerState {
+        return KeyguardControllerState.from(
+            isAodShowing = proto?.aodShowing ?: false,
+            isKeyguardShowing = proto?.keyguardShowing ?: false,
+            keyguardOccludedStates =
+                proto?.keyguardOccludedStates?.associate { it.displayId to it.keyguardOccluded }
+                    ?: emptyMap()
+        )
+    }
+
+    private fun createWindowContainerChild(
+        proto: WindowContainerChildProto,
+        isActivityInTree: Boolean
+    ): WindowContainer? {
+        return createDisplayContent(proto.displayContent, isActivityInTree)
+            ?: createDisplayArea(proto.displayArea, isActivityInTree)
+                ?: createTask(proto.task, isActivityInTree)
+                ?: createTaskFragment(proto.taskFragment, isActivityInTree)
+                ?: createActivity(proto.activity)
+                ?: createWindowToken(proto.windowToken, isActivityInTree)
+                ?: createWindowState(proto.window, isActivityInTree)
+                ?: createWindowContainer(proto.windowContainer, children = emptyList())
+    }
+
+    private fun createDisplayContent(
+        proto: DisplayContentProto?,
+        isActivityInTree: Boolean
+    ): DisplayContent? {
+        return if (proto == null) {
+            null
+        } else {
+            DisplayContent(
+                id = proto.id,
+                focusedRootTaskId = proto.focusedRootTaskId,
+                resumedActivity = proto.resumedActivity?.title ?: "",
+                singleTaskInstance = proto.singleTaskInstance,
+                defaultPinnedStackBounds = proto.pinnedTaskController?.defaultBounds?.toRect()
+                        ?: Rect.EMPTY,
+                pinnedStackMovementBounds = proto.pinnedTaskController?.movementBounds?.toRect()
+                        ?: Rect.EMPTY,
+                displayRect =
+                    Rect.from(
+                        0,
+                        0,
+                        proto.displayInfo?.logicalWidth ?: 0,
+                        proto.displayInfo?.logicalHeight ?: 0
+                    ),
+                appRect =
+                    Rect.from(
+                        0,
+                        0,
+                        proto.displayInfo?.appWidth ?: 0,
+                        proto.displayInfo?.appHeight ?: 0
+                    ),
+                dpi = proto.dpi,
+                flags = proto.displayInfo?.flags ?: 0,
+                stableBounds = proto.displayFrames?.stableBounds?.toRect() ?: Rect.EMPTY,
+                surfaceSize = proto.surfaceSize,
+                focusedApp = proto.focusedApp,
+                lastTransition =
+                    appTransitionToString(proto.appTransition?.lastUsedAppTransition ?: 0),
+                appTransitionState = appStateToString(proto.appTransition?.appTransitionState ?: 0),
+                rotation =
+                    Rotation.getByValue(proto.displayRotation?.rotation ?: Surface.ROTATION_0),
+                lastOrientation = proto.displayRotation?.lastOrientation ?: 0,
+                cutout = createDisplayCutout(proto.displayInfo?.cutout),
+                windowContainer =
+                    createWindowContainer(
+                        proto.rootDisplayArea.windowContainer,
+                        proto.rootDisplayArea.windowContainer.children.mapNotNull { p ->
+                            createWindowContainerChild(p, isActivityInTree)
+                        },
+                        nameOverride = proto.displayInfo?.name ?: ""
+                    )
+                        ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createDisplayArea(
+        proto: DisplayAreaProto?,
+        isActivityInTree: Boolean
+    ): DisplayArea? {
+        return if (proto == null) {
+            null
+        } else {
+            DisplayArea(
+                isTaskDisplayArea = proto.isTaskDisplayArea,
+                windowContainer =
+                    createWindowContainer(
+                        proto.windowContainer,
+                        proto.windowContainer.children.mapNotNull { p ->
+                            createWindowContainerChild(p, isActivityInTree)
+                        }
+                    )
+                        ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createTask(proto: TaskProto?, isActivityInTree: Boolean): Task? {
+        return if (proto == null) {
+            null
+        } else {
+            Task(
+                activityType = proto.taskFragment?.activityType ?: proto.activityType,
+                isFullscreen = proto.fillsParent,
+                bounds = proto.bounds.toRect(),
+                taskId = proto.id,
+                rootTaskId = proto.rootTaskId,
+                displayId = proto.taskFragment?.displayId ?: proto.displayId,
+                lastNonFullscreenBounds = proto.lastNonFullscreenBounds?.toRect() ?: Rect.EMPTY,
+                realActivity = proto.realActivity,
+                origActivity = proto.origActivity,
+                resizeMode = proto.resizeMode,
+                _resumedActivity = proto.resumedActivity?.title ?: "",
+                animatingBounds = proto.animatingBounds,
+                surfaceWidth = proto.surfaceWidth,
+                surfaceHeight = proto.surfaceHeight,
+                createdByOrganizer = proto.createdByOrganizer,
+                minWidth = proto.taskFragment?.minWidth ?: proto.minWidth,
+                minHeight = proto.taskFragment?.minHeight ?: proto.minHeight,
+                windowContainer =
+                    createWindowContainer(
+                        proto.taskFragment?.windowContainer ?: proto.windowContainer,
+                        if (proto.taskFragment != null) {
+                            proto.taskFragment.windowContainer.children.mapNotNull { p ->
+                                createWindowContainerChild(p, isActivityInTree)
+                            }
+                        } else {
+                            proto.windowContainer.children.mapNotNull { p ->
+                                createWindowContainerChild(p, isActivityInTree)
+                            }
+                        }
+                    )
+                        ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createTaskFragment(
+        proto: TaskFragmentProto?,
+        isActivityInTree: Boolean
+    ): TaskFragment? {
+        return if (proto == null) {
+            null
+        } else {
+            TaskFragment(
+                activityType = proto.activityType,
+                displayId = proto.displayId,
+                minWidth = proto.minWidth,
+                minHeight = proto.minHeight,
+                windowContainer =
+                    createWindowContainer(
+                        proto.windowContainer,
+                        proto.windowContainer.children.mapNotNull { p ->
+                            createWindowContainerChild(p, isActivityInTree)
+                        }
+                    )
+                        ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createActivity(proto: ActivityRecordProto?): Activity? {
+        return if (proto == null) {
+            null
+        } else {
+            Activity(
+                name = proto.name,
+                state = proto.state,
+                visible = proto.visible,
+                frontOfTask = proto.frontOfTask,
+                procId = proto.procId,
+                isTranslucent = proto.translucent,
+                windowContainer =
+                    createWindowContainer(
+                        proto.windowToken.windowContainer,
+                        proto.windowToken.windowContainer.children.mapNotNull { p ->
+                            createWindowContainerChild(p, isActivityInTree = true)
+                        }
+                    )
+                        ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createWindowToken(
+        proto: WindowTokenProto?,
+        isActivityInTree: Boolean
+    ): WindowToken? {
+        return if (proto == null) {
+            null
+        } else {
+            WindowToken(
+                createWindowContainer(
+                    proto.windowContainer,
+                    proto.windowContainer.children.mapNotNull { p ->
+                        createWindowContainerChild(p, isActivityInTree)
+                    }
+                )
+                    ?: error("Window container should not be null")
+            )
+        }
+    }
+
+    private fun createWindowState(
+        proto: WindowStateProto?,
+        isActivityInTree: Boolean
+    ): WindowState? {
+        return if (proto == null) {
+            null
+        } else {
+            val identifierName = proto.windowContainer.identifier?.title ?: ""
+            WindowState(
+                attributes = createWindowLayerParams(proto.attributes),
+                displayId = proto.displayId,
+                stackId = proto.stackId,
+                layer = proto.animator?.surface?.layer ?: 0,
+                isSurfaceShown = proto.animator?.surface?.shown ?: false,
+                windowType =
+                    when {
+                        identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX) ->
+                            WindowState.WINDOW_TYPE_STARTING
+                        proto.animatingExit -> WindowState.WINDOW_TYPE_EXITING
+                        identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX) ->
+                            WindowState.WINDOW_TYPE_STARTING
+                        else -> 0
+                    },
+                requestedSize = Size.from(proto.requestedWidth, proto.requestedHeight),
+                surfacePosition = proto.surfacePosition?.toRect(),
+                frame = proto.windowFrames?.frame?.toRect() ?: Rect.EMPTY,
+                containingFrame = proto.windowFrames?.containingFrame?.toRect() ?: Rect.EMPTY,
+                parentFrame = proto.windowFrames?.parentFrame?.toRect() ?: Rect.EMPTY,
+                contentFrame = proto.windowFrames?.contentFrame?.toRect() ?: Rect.EMPTY,
+                contentInsets = proto.windowFrames?.contentInsets?.toRect() ?: Rect.EMPTY,
+                surfaceInsets = proto.surfaceInsets?.toRect() ?: Rect.EMPTY,
+                givenContentInsets = proto.givenContentInsets?.toRect() ?: Rect.EMPTY,
+                crop = proto.animator?.lastClipRect?.toRect() ?: Rect.EMPTY,
+                windowContainer =
+                    createWindowContainer(
+                        proto.windowContainer,
+                        proto.windowContainer.children.mapNotNull { p ->
+                            createWindowContainerChild(p, isActivityInTree)
+                        },
+                        nameOverride =
+                            when {
+                                // Existing code depends on the prefix being removed
+                                identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX) ->
+                                    identifierName.substring(
+                                        WindowState.STARTING_WINDOW_PREFIX.length
+                                    )
+                                identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX) ->
+                                    identifierName.substring(
+                                        WindowState.DEBUGGER_WINDOW_PREFIX.length
+                                    )
+                                else -> identifierName
+                            }
+                    )
+                        ?: error("Window container should not be null"),
+                isAppWindow = isActivityInTree
+            )
+        }
+    }
+
+    private fun createWindowLayerParams(proto: WindowLayoutParamsProto?): WindowLayoutParams {
+        return WindowLayoutParams.from(
+            type = proto?.type ?: 0,
+            x = proto?.x ?: 0,
+            y = proto?.y ?: 0,
+            width = proto?.width ?: 0,
+            height = proto?.height ?: 0,
+            horizontalMargin = proto?.horizontalMargin ?: 0f,
+            verticalMargin = proto?.verticalMargin ?: 0f,
+            gravity = proto?.gravity ?: 0,
+            softInputMode = proto?.softInputMode ?: 0,
+            format = proto?.format ?: 0,
+            windowAnimations = proto?.windowAnimations ?: 0,
+            alpha = proto?.alpha ?: 0f,
+            screenBrightness = proto?.screenBrightness ?: 0f,
+            buttonBrightness = proto?.buttonBrightness ?: 0f,
+            rotationAnimation = proto?.rotationAnimation ?: 0,
+            preferredRefreshRate = proto?.preferredRefreshRate ?: 0f,
+            preferredDisplayModeId = proto?.preferredDisplayModeId ?: 0,
+            hasSystemUiListeners = proto?.hasSystemUiListeners ?: false,
+            inputFeatureFlags = proto?.inputFeatureFlags ?: 0,
+            userActivityTimeout = proto?.userActivityTimeout ?: 0,
+            colorMode = proto?.colorMode ?: 0,
+            flags = proto?.flags ?: 0,
+            privateFlags = proto?.privateFlags ?: 0,
+            systemUiVisibilityFlags = proto?.systemUiVisibilityFlags ?: 0,
+            subtreeSystemUiVisibilityFlags = proto?.subtreeSystemUiVisibilityFlags ?: 0,
+            appearance = proto?.appearance ?: 0,
+            behavior = proto?.behavior ?: 0,
+            fitInsetsTypes = proto?.fitInsetsTypes ?: 0,
+            fitInsetsSides = proto?.fitInsetsSides ?: 0,
+            fitIgnoreVisibility = proto?.fitIgnoreVisibility ?: false
+        )
+    }
+
+    private fun createConfigurationContainer(
+        proto: ConfigurationContainerProto?
+    ): ConfigurationContainer {
+        return ConfigurationContainer(
+            overrideConfiguration = createConfiguration(proto?.overrideConfiguration),
+            fullConfiguration = createConfiguration(proto?.fullConfiguration),
+            mergedOverrideConfiguration = createConfiguration(proto?.mergedOverrideConfiguration)
+        )
+    }
+
+    private fun createConfiguration(proto: ConfigurationProto?): Configuration? {
+        return if (proto == null) {
+            null
+        } else {
+            Configuration.from(
+                windowConfiguration =
+                    if (proto.windowConfiguration != null) {
+                        createWindowConfiguration(proto.windowConfiguration)
+                    } else {
+                        null
+                    },
+                densityDpi = proto.densityDpi,
+                orientation = proto.orientation,
+                screenHeightDp = proto.screenHeightDp,
+                screenWidthDp = proto.screenWidthDp,
+                smallestScreenWidthDp = proto.smallestScreenWidthDp,
+                screenLayout = proto.screenLayout,
+                uiMode = proto.uiMode
+            )
+        }
+    }
+
+    private fun createWindowConfiguration(proto: WindowConfigurationProto): WindowConfiguration {
+        return WindowConfiguration.from(
+            appBounds = proto.appBounds?.toRect(),
+            bounds = proto.bounds?.toRect(),
+            maxBounds = proto.maxBounds?.toRect(),
+            windowingMode = proto.windowingMode,
+            activityType = proto.activityType
+        )
+    }
+
+    private fun createWindowContainer(
+        proto: WindowContainerProto?,
+        children: List<WindowContainer>,
+        nameOverride: String? = null
+    ): WindowContainer? {
+        return if (proto == null) {
+            null
+        } else {
+            WindowContainer(
+                title = nameOverride ?: proto.identifier?.title ?: "",
+                token = proto.identifier?.hashCode?.toString(16) ?: "",
+                orientation = proto.orientation,
+                _isVisible = proto.visible,
+                configurationContainer = createConfigurationContainer(proto.configurationContainer),
+                layerId = proto.surfaceControl?.layerId ?: 0,
+                children = children.toTypedArray(),
+                computedZ = computedZCounter++
+            )
+        }
+    }
+
+    private fun createDisplayCutout(proto: DisplayCutoutProto?): DisplayCutout? {
+        return if (proto == null) {
+            null
+        } else {
+            DisplayCutout(
+                proto.insets?.toInsets() ?: Insets.EMPTY,
+                proto.boundLeft?.toRect() ?: Rect.EMPTY,
+                proto.boundTop?.toRect() ?: Rect.EMPTY,
+                proto.boundRight?.toRect() ?: Rect.EMPTY,
+                proto.boundBottom?.toRect() ?: Rect.EMPTY,
+                proto.waterfallInsets?.toInsets() ?: Insets.EMPTY
+            )
+        }
+    }
+
+    private fun appTransitionToString(transition: Int): String {
+        return when (transition) {
+            ViewProtoEnums.TRANSIT_UNSET -> "TRANSIT_UNSET"
+            ViewProtoEnums.TRANSIT_NONE -> "TRANSIT_NONE"
+            ViewProtoEnums.TRANSIT_ACTIVITY_OPEN -> TRANSIT_ACTIVITY_OPEN
+            ViewProtoEnums.TRANSIT_ACTIVITY_CLOSE -> TRANSIT_ACTIVITY_CLOSE
+            ViewProtoEnums.TRANSIT_TASK_OPEN -> TRANSIT_TASK_OPEN
+            ViewProtoEnums.TRANSIT_TASK_CLOSE -> TRANSIT_TASK_CLOSE
+            ViewProtoEnums.TRANSIT_TASK_TO_FRONT -> "TRANSIT_TASK_TO_FRONT"
+            ViewProtoEnums.TRANSIT_TASK_TO_BACK -> "TRANSIT_TASK_TO_BACK"
+            ViewProtoEnums.TRANSIT_WALLPAPER_CLOSE -> TRANSIT_WALLPAPER_CLOSE
+            ViewProtoEnums.TRANSIT_WALLPAPER_OPEN -> TRANSIT_WALLPAPER_OPEN
+            ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_OPEN -> TRANSIT_WALLPAPER_INTRA_OPEN
+            ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_CLOSE -> TRANSIT_WALLPAPER_INTRA_CLOSE
+            ViewProtoEnums.TRANSIT_TASK_OPEN_BEHIND -> "TRANSIT_TASK_OPEN_BEHIND"
+            ViewProtoEnums.TRANSIT_ACTIVITY_RELAUNCH -> "TRANSIT_ACTIVITY_RELAUNCH"
+            ViewProtoEnums.TRANSIT_DOCK_TASK_FROM_RECENTS -> "TRANSIT_DOCK_TASK_FROM_RECENTS"
+            ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY -> TRANSIT_KEYGUARD_GOING_AWAY
+            ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER ->
+                TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+            ViewProtoEnums.TRANSIT_KEYGUARD_OCCLUDE -> TRANSIT_KEYGUARD_OCCLUDE
+            ViewProtoEnums.TRANSIT_KEYGUARD_UNOCCLUDE -> TRANSIT_KEYGUARD_UNOCCLUDE
+            ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN -> TRANSIT_TRANSLUCENT_ACTIVITY_OPEN
+            ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE -> TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE
+            ViewProtoEnums.TRANSIT_CRASHING_ACTIVITY_CLOSE -> "TRANSIT_CRASHING_ACTIVITY_CLOSE"
+            else -> error("Invalid lastUsedAppTransition")
+        }
+    }
+
+    private fun appStateToString(appState: Int): String {
+        return when (appState) {
+            AppTransitionProto.APP_STATE_IDLE -> "APP_STATE_IDLE"
+            AppTransitionProto.APP_STATE_READY -> "APP_STATE_READY"
+            AppTransitionProto.APP_STATE_RUNNING -> "APP_STATE_RUNNING"
+            AppTransitionProto.APP_STATE_TIMEOUT -> "APP_STATE_TIMEOUT"
+            else -> error("Invalid AppTransitionState")
+        }
+    }
+
+    private fun RectProto.toRect() = Rect.from(this.left, this.top, this.right, this.bottom)
+
+    private fun RectProto.toInsets() = Insets.from(this.left, this.top, this.right, this.bottom)
+
+    companion object {
+        private const val TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN"
+        private const val TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE"
+        private const val TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN"
+        private const val TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE"
+        private const val TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN"
+        private const val TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE"
+        private const val TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN"
+        private const val TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE"
+        private const val TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY"
+        private const val TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
+            "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER"
+        private const val TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE"
+        private const val TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE"
+        private const val TRANSIT_TRANSLUCENT_ACTIVITY_OPEN = "TRANSIT_TRANSLUCENT_ACTIVITY_OPEN"
+        private const val TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE = "TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE"
+    }
+}
diff --git a/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParser.kt b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParser.kt
new file mode 100644
index 0000000..64f4873
--- /dev/null
+++ b/libraries/flicker/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParser.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+import com.android.server.wm.nano.WindowManagerTraceFileProto
+import com.android.server.wm.nano.WindowManagerTraceProto
+
+/** Parser for [WindowManagerTrace] objects containing traces */
+open class WindowManagerTraceParser(private val legacyTrace: Boolean = false) :
+    AbstractTraceParser<
+        WindowManagerTraceFileProto, WindowManagerTraceProto, WindowManagerState, WindowManagerTrace
+    >() {
+    private var realToElapsedTimeOffsetNanos = 0L
+
+    override val traceName: String = "WM Trace"
+
+    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerTraceFileProto =
+        WindowManagerTraceFileProto.parseFrom(bytes)
+
+    override fun createTrace(entries: List<WindowManagerState>): WindowManagerTrace =
+        WindowManagerTrace(entries.toTypedArray())
+
+    override fun getEntries(input: WindowManagerTraceFileProto): List<WindowManagerTraceProto> =
+        input.entry.toList()
+
+    override fun getTimestamp(entry: WindowManagerTraceProto): Timestamp {
+        require(legacyTrace || realToElapsedTimeOffsetNanos != 0L)
+        return CrossPlatform.timestamp.from(
+            elapsedNanos = entry.elapsedRealtimeNanos,
+            unixNanos = entry.elapsedRealtimeNanos + realToElapsedTimeOffsetNanos
+        )
+    }
+
+    override fun onBeforeParse(input: WindowManagerTraceFileProto) {
+        realToElapsedTimeOffsetNanos = input.realToElapsedTimeOffsetNanos
+    }
+
+    override fun doParseEntry(entry: WindowManagerTraceProto): WindowManagerState {
+        return WindowManagerStateBuilder()
+            .atPlace(entry.where)
+            .forTimestamp(entry.elapsedRealtimeNanos)
+            .withRealTimeOffset(realToElapsedTimeOffsetNanos)
+            .forProto(entry.windowManagerService)
+            .build()
+    }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/AbstractFlickerTestData.kt b/libraries/flicker/src/com/android/server/wm/flicker/AbstractFlickerTestData.kt
deleted file mode 100644
index 72b6030..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/AbstractFlickerTestData.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.flicker
-
-abstract class AbstractFlickerTestData : IFlickerTestData {
-    private var assertionsCheckedCallback: ((Boolean) -> Unit)? = null
-    private var createTagCallback: (String) -> Unit = {}
-
-    final override fun withTag(tag: String, commands: IFlickerTestData.() -> Any) {
-        commands()
-        createTagCallback(tag)
-    }
-
-    final override fun createTag(tag: String) {
-        withTag(tag) {}
-    }
-
-    final override fun clearTagListener() {
-        assertionsCheckedCallback = {}
-    }
-
-    final override fun setAssertionsCheckedCallback(callback: (Boolean) -> Unit) {
-        assertionsCheckedCallback = callback
-    }
-
-    final override fun setCreateTagListener(callback: (String) -> Unit) {
-        createTagCallback = callback
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/Extensions.kt b/libraries/flicker/src/com/android/server/wm/flicker/Extensions.kt
deleted file mode 100644
index 004350f..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/Extensions.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-@file:JvmName("Extensions")
-
-package com.android.server.wm.flicker
-
-import android.os.SystemClock
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.SECOND_AS_NANOSECONDS
-import com.android.server.wm.traces.common.Timestamp
-import java.io.File
-import java.time.Instant
-
-/**
- * Gets the default flicker output dir. By default, the data is stored in /sdcard/flicker instead of
- * using the app's internal data directory to be accessible by other components (i.e. FilePuller)
- */
-fun getDefaultFlickerOutputDir() = File("/sdcard/flicker")
-
-internal fun String.containsAny(vararg values: String): Boolean {
-    return values.isEmpty() || values.any { search -> this.contains(search) }
-}
-
-/** @return the current timestamp as [Timestamp] */
-fun now(): Timestamp {
-    val now = Instant.now()
-    return CrossPlatform.timestamp.from(
-        elapsedNanos = SystemClock.elapsedRealtimeNanos(),
-        systemUptimeNanos = SystemClock.uptimeNanos(),
-        unixNanos = now.epochSecond * SECOND_AS_NANOSECONDS + now.nano
-    )
-}
-
-fun File.deleteIfExists(): Boolean =
-    if (this.exists()) {
-        this.delete()
-    } else {
-        false
-    }
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerBuilder.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerBuilder.kt
deleted file mode 100644
index e6e96df..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerBuilder.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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.flicker
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.monitor.EventLogMonitor
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor
-import com.android.server.wm.flicker.monitor.NoTraceMonitor
-import com.android.server.wm.flicker.monitor.ScreenRecorder
-import com.android.server.wm.flicker.monitor.TransactionsTraceMonitor
-import com.android.server.wm.flicker.monitor.TransitionsTraceMonitor
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-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 java.io.File
-
-/** Build Flicker tests using Flicker DSL */
-@FlickerDslMarker
-class FlickerBuilder(
-    private val instrumentation: Instrumentation,
-    private val outputDir: File = getDefaultFlickerOutputDir(),
-    private val wmHelper: WindowManagerStateHelper =
-        WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false),
-    private val setupCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
-    private val transitionCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
-    private val teardownCommands: MutableList<IFlickerTestData.() -> Any> = mutableListOf(),
-    val device: UiDevice = UiDevice.getInstance(instrumentation),
-    private val traceMonitors: MutableList<ITransitionMonitor> =
-        mutableListOf<ITransitionMonitor>().also {
-            it.add(WindowManagerTraceMonitor())
-            it.add(LayersTraceMonitor())
-            if (isShellTransitionsEnabled) {
-                // Transition tracing only works if shell transitions are enabled.
-                it.add(TransitionsTraceMonitor())
-            }
-            it.add(TransactionsTraceMonitor())
-            it.add(ScreenRecorder(instrumentation.targetContext))
-            it.add(EventLogMonitor())
-        }
-) {
-    private var usingExistingTraces = false
-
-    /**
-     * Configure a [WindowManagerTraceMonitor] to obtain [WindowManagerTrace]
-     *
-     * By default, the tracing is always active. To disable tracing return null
-     *
-     * If this tracing is disabled, the assertions for [WindowManagerTrace] and [WindowManagerState]
-     * will not be executed
-     */
-    fun withWindowManagerTracing(traceMonitor: () -> WindowManagerTraceMonitor?): FlickerBuilder =
-        apply {
-            traceMonitors.removeIf { it is WindowManagerTraceMonitor }
-            addMonitor(traceMonitor())
-        }
-
-    /** Disable [LayersTraceMonitor]. */
-    fun withoutLayerTracing(): FlickerBuilder = apply { withLayerTracing { null } }
-
-    /**
-     * Configure a [LayersTraceMonitor] to obtain [LayersTrace].
-     *
-     * By default the tracing is always active. To disable tracing return null
-     *
-     * If this tracing is disabled, the assertions for [LayersTrace] and [BaseLayerTraceEntry] will
-     * not be executed
-     */
-    fun withLayerTracing(traceMonitor: () -> LayersTraceMonitor?): FlickerBuilder = apply {
-        traceMonitors.removeIf { it is LayersTraceMonitor }
-        addMonitor(traceMonitor())
-    }
-
-    /** Disable [TransitionsTraceMonitor]. */
-    fun withoutTransitionTracing(): FlickerBuilder = apply { withTransitionTracing { null } }
-
-    /**
-     * Configure a [TransitionsTraceMonitor] to obtain [TransitionsTrace].
-     *
-     * By default, shell transition tracing is disabled.
-     */
-    fun withTransitionTracing(traceMonitor: () -> TransitionsTraceMonitor?): FlickerBuilder =
-        apply {
-            traceMonitors.removeIf { it is TransitionsTraceMonitor }
-            addMonitor(traceMonitor())
-        }
-
-    /** Disable [TransactionsTraceMonitor]. */
-    fun withoutTransactionsTracing(): FlickerBuilder = apply { withTransactionsTracing { null } }
-
-    /**
-     * Configure a [TransactionsTraceMonitor] to obtain [TransactionsTrace].
-     *
-     * By default, shell transition tracing is disabled.
-     */
-    fun withTransactionsTracing(traceMonitor: () -> TransactionsTraceMonitor?): FlickerBuilder =
-        apply {
-            traceMonitors.removeIf { it is TransactionsTraceMonitor }
-            addMonitor(traceMonitor())
-        }
-
-    /**
-     * Configure a [ScreenRecorder].
-     *
-     * By default, the tracing is always active. To disable tracing return null
-     */
-    fun withScreenRecorder(screenRecorder: () -> ScreenRecorder?): FlickerBuilder = apply {
-        traceMonitors.removeIf { it is ScreenRecorder }
-        addMonitor(screenRecorder())
-    }
-
-    fun withoutScreenRecorder(): FlickerBuilder = apply {
-        traceMonitors.removeIf { it is ScreenRecorder }
-    }
-
-    /** Defines the setup commands executed before the [transitions] to test */
-    fun setup(commands: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
-        setupCommands.add(commands)
-    }
-
-    /** Defines the teardown commands executed after the [transitions] to test */
-    fun teardown(commands: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
-        teardownCommands.add(commands)
-    }
-
-    /** Defines the commands that trigger the behavior to test */
-    fun transitions(command: IFlickerTestData.() -> Unit): FlickerBuilder = apply {
-        require(!usingExistingTraces) {
-            "Can't update transition after calling usingExistingTraces"
-        }
-        transitionCommands.add(command)
-    }
-
-    data class TraceFiles(
-        val wmTrace: File,
-        val layersTrace: File,
-        val transactions: File,
-        val transitions: File,
-        val eventLog: File
-    )
-
-    /** Use pre-executed results instead of running transitions to get the traces */
-    fun usingExistingTraces(_traceFiles: () -> TraceFiles): FlickerBuilder = apply {
-        val traceFiles = _traceFiles()
-        // Remove all trace monitor and use only monitor that read from existing trace file
-        this.traceMonitors.clear()
-        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.WM, traceFiles.wmTrace) })
-        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.SF, traceFiles.layersTrace) })
-        addMonitor(
-            NoTraceMonitor { it.addTraceResult(TraceType.TRANSACTION, traceFiles.transactions) }
-        )
-        addMonitor(
-            NoTraceMonitor { it.addTraceResult(TraceType.TRANSITION, traceFiles.transitions) }
-        )
-        addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.EVENT_LOG, traceFiles.eventLog) })
-
-        // Remove all transitions execution
-        this.transitionCommands.clear()
-        this.usingExistingTraces = true
-    }
-
-    /** Creates a new Flicker runner based on the current builder configuration */
-    fun build(): IFlickerTestData {
-        return FlickerTestData(
-            instrumentation,
-            device,
-            outputDir,
-            traceMonitors,
-            setupCommands,
-            transitionCommands,
-            teardownCommands,
-            wmHelper
-        )
-    }
-
-    /** Returns a copy of the current builder with the changes of [block] applied */
-    fun copy(block: FlickerBuilder.() -> Unit) =
-        FlickerBuilder(
-                instrumentation,
-                outputDir.absoluteFile,
-                wmHelper,
-                setupCommands.toMutableList(),
-                transitionCommands.toMutableList(),
-                teardownCommands.toMutableList(),
-                device,
-                traceMonitors.toMutableList(),
-            )
-            .apply(block)
-
-    private fun addMonitor(newMonitor: ITransitionMonitor?) {
-        require(!usingExistingTraces) { "Can't add monitors after calling usingExistingTraces" }
-
-        if (newMonitor != null) {
-            traceMonitors.add(newMonitor)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTag.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerTag.kt
deleted file mode 100644
index 5870705..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTag.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker
-
-object FlickerTag {
-    val TRANSITION_START = 89001
-    val TRANSITION_END = 89002
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTest.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerTest.kt
deleted file mode 100644
index 1f28b3c..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTest.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * 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.flicker
-
-import com.android.server.wm.flicker.assertions.AssertionDataFactory
-import com.android.server.wm.flicker.assertions.AssertionStateDataFactory
-import com.android.server.wm.flicker.assertions.BaseAssertionRunner
-import com.android.server.wm.flicker.datastore.CachedAssertionRunner
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.Scenario
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.region.RegionTraceSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Specification of a flicker test for JUnit ParameterizedRunner class */
-data class FlickerTest(
-    private val scenarioBuilder: ScenarioBuilder = ScenarioBuilder(),
-    private val resultReaderProvider: (IScenario) -> CachedResultReader = {
-        CachedResultReader(it, DEFAULT_TRACE_CONFIG)
-    },
-    private val subjectsParserProvider: (IReader) -> SubjectsParser = { SubjectsParser(it) },
-    private val runnerProvider: (Scenario) -> BaseAssertionRunner = {
-        val reader = resultReaderProvider(it)
-        val subjectsParser = subjectsParserProvider(reader)
-        CachedAssertionRunner(it, reader, subjectsParser)
-    }
-) {
-    private val wmAssertionFactory =
-        AssertionDataFactory(WindowManagerStateSubject::class, WindowManagerTraceSubject::class)
-    private val layersAssertionFactory =
-        AssertionDataFactory(LayerTraceEntrySubject::class, LayersTraceSubject::class)
-    private val eventLogAssertionFactory = AssertionStateDataFactory(EventLogSubject::class)
-
-    var scenario: Scenario = ScenarioBuilder().createEmptyScenario()
-        private set
-
-    override fun toString(): String = scenario.toString()
-
-    fun initialize(testClass: String): Scenario {
-        scenario = scenarioBuilder.forClass(testClass).build()
-        return scenario
-    }
-
-    fun <T> getConfigValue(key: String) = scenario.getConfigValue<T>(key)
-
-    /** Obtains a reader for the flicker result artifact */
-    val reader: IReader
-        get() = resultReaderProvider(scenario)
-
-    /**
-     * Execute [assertion] on the initial state of a WM trace (before transition)
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertWmStart(assertion: WindowManagerStateSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertWmStart") {
-            val assertionData =
-                wmAssertionFactory.createStartStateAssertion(assertion as FlickerSubject.() -> Unit)
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on the final state of a WM trace (after transition)
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertWmEnd(assertion: WindowManagerStateSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertWmEnd") {
-            val wrappedAssertion: (WindowManagerStateSubject) -> Unit = { assertion(it) }
-            val assertionData =
-                wmAssertionFactory.createEndStateAssertion(
-                    wrappedAssertion as (FlickerSubject) -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on a WM trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertWm(assertion: WindowManagerTraceSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertWm") {
-            val assertionData =
-                wmAssertionFactory.createTraceAssertion(
-                    assertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on a user defined moment ([tag]) of a WM trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertWmTag(tag: String, assertion: WindowManagerStateSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertWmTag") {
-            val assertionData =
-                wmAssertionFactory.createTagAssertion(tag, assertion as FlickerSubject.() -> Unit)
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on the visible region of WM state matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     * @param assertion Assertion predicate
-     */
-    fun assertWmVisibleRegion(
-        componentMatcher: IComponentMatcher,
-        assertion: RegionTraceSubject.() -> Unit
-    ) {
-        CrossPlatform.log.withTracing("assertWmVisibleRegion") {
-            val assertionData = buildWmVisibleRegionAssertion(componentMatcher, assertion)
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on the initial state of a SF trace (before transition)
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertLayersStart(assertion: LayerTraceEntrySubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertLayersStart") {
-            val assertionData =
-                layersAssertionFactory.createStartStateAssertion(
-                    assertion as FlickerSubject.() -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on the final state of a SF trace (after transition)
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertLayersEnd(assertion: LayerTraceEntrySubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertLayersEnd") {
-            val assertionData =
-                layersAssertionFactory.createEndStateAssertion(
-                    assertion as FlickerSubject.() -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on a SF trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertLayers(assertion: LayersTraceSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertLayers") {
-            val assertionData =
-                layersAssertionFactory.createTraceAssertion(
-                    assertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on a user defined moment ([tag]) of a SF trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertLayersTag(tag: String, assertion: LayerTraceEntrySubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertLayersTag") {
-            val assertionData =
-                layersAssertionFactory.createTagAssertion(
-                    tag,
-                    assertion as FlickerSubject.() -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on the visible region of a component on the layers trace matching
-     * [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
-     * Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise, calculates the
-     * visible region when the information is not available from the CE
-     * @param assertion Assertion predicate
-     */
-    @JvmOverloads
-    fun assertLayersVisibleRegion(
-        componentMatcher: IComponentMatcher,
-        useCompositionEngineRegionOnly: Boolean = true,
-        assertion: RegionTraceSubject.() -> Unit
-    ) {
-        CrossPlatform.log.withTracing("assertLayersVisibleRegion") {
-            val assertionData =
-                buildLayersVisibleRegionAssertion(
-                    componentMatcher,
-                    useCompositionEngineRegionOnly,
-                    assertion
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    /**
-     * Execute [assertion] on a sequence of event logs
-     *
-     * @param assertion Assertion predicate
-     */
-    fun assertEventLog(assertion: EventLogSubject.() -> Unit) {
-        CrossPlatform.log.withTracing("assertEventLog") {
-            val assertionData =
-                eventLogAssertionFactory.createTagAssertion(
-                    AssertionTag.ALL,
-                    assertion as FlickerSubject.() -> Unit
-                )
-            doRunAssertion(assertionData)
-        }
-    }
-
-    private fun doRunAssertion(assertion: AssertionData) {
-        require(!scenario.isEmpty) { "Scenario shouldn't be empty" }
-        runnerProvider.invoke(scenario).runAssertion(assertion)?.let { throw it }
-    }
-
-    private fun buildWmVisibleRegionAssertion(
-        componentMatcher: IComponentMatcher,
-        assertion: RegionTraceSubject.() -> Unit
-    ): AssertionData {
-        val closedAssertion: WindowManagerTraceSubject.() -> Unit = {
-            require(!hasAssertions()) { "Subject was already used to execute assertions" }
-            // convert WindowManagerTraceSubject to RegionTraceSubject
-            val regionTraceSubject = visibleRegion(componentMatcher)
-            // add assertions to the regionTraceSubject's AssertionChecker
-            assertion(regionTraceSubject)
-            // loop through all entries to validate assertions
-            regionTraceSubject.forAllEntries()
-        }
-
-        return wmAssertionFactory.createTraceAssertion(
-            closedAssertion as (FlickerTraceSubject<FlickerSubject>) -> Unit
-        )
-    }
-
-    private fun buildLayersVisibleRegionAssertion(
-        componentMatcher: IComponentMatcher,
-        useCompositionEngineRegionOnly: Boolean = true,
-        assertion: RegionTraceSubject.() -> Unit
-    ): AssertionData {
-        val closedAssertion: LayersTraceSubject.() -> Unit = {
-            require(!hasAssertions()) { "Subject was already used to execute assertions" }
-            // convert LayersTraceSubject to RegionTraceSubject
-            val regionTraceSubject = visibleRegion(componentMatcher, useCompositionEngineRegionOnly)
-
-            // add assertions to the regionTraceSubject's AssertionChecker
-            assertion(regionTraceSubject)
-            // loop through all entries to validate assertions
-            regionTraceSubject.forAllEntries()
-        }
-
-        return layersAssertionFactory.createTraceAssertion(
-            closedAssertion as (FlickerTraceSubject<*>) -> Unit
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestData.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestData.kt
deleted file mode 100644
index ff2198a..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestData.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import java.io.File
-
-@DslMarker annotation class FlickerDslMarker
-
-/**
- * Defines the runner for the flicker tests. This component is responsible for running the flicker
- * tests and executing assertions on the traces to check for inconsistent behaviors on
- * [WindowManagerTrace] and [LayersTrace]
- */
-@FlickerDslMarker
-open class FlickerTestData(
-    /** Instrumentation to run the tests */
-    override val instrumentation: Instrumentation,
-    /** Test automation component used to interact with the device */
-    override val device: UiDevice,
-    /** Output directory for test results */
-    override val outputDir: File,
-    /** Enabled tracing monitors */
-    override val traceMonitors: List<ITransitionMonitor>,
-    /** Commands to be executed before the transition */
-    override val transitionSetup: List<IFlickerTestData.() -> Any>,
-    /** Test commands */
-    override val transitions: List<IFlickerTestData.() -> Any>,
-    /** Commands to be executed after the transition */
-    override val transitionTeardown: List<IFlickerTestData.() -> Any>,
-    /** Helper object for WM Synchronization */
-    override val wmHelper: WindowManagerStateHelper
-) : AbstractFlickerTestData()
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestFactory.kt b/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestFactory.kt
deleted file mode 100644
index ee7344e..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/FlickerTestFactory.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.flicker
-
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.service.PlatformConsts
-
-/**
- * Factory for creating JUnit4 compatible tests based on the flicker DSL
- *
- * This class recreates behavior from JUnit5 TestFactory that is not available on JUnit4
- */
-object FlickerTestFactory {
-    /**
-     * Gets a list of test configurations.
-     *
-     * Each configuration has only a start orientation.
-     */
-    @JvmOverloads
-    @JvmStatic
-    fun nonRotationTests(
-        supportedRotations: List<PlatformConsts.Rotation> =
-            listOf(PlatformConsts.Rotation.ROTATION_0, PlatformConsts.Rotation.ROTATION_90),
-        supportedNavigationModes: List<PlatformConsts.NavBar> =
-            listOf(PlatformConsts.NavBar.MODE_3BUTTON, PlatformConsts.NavBar.MODE_GESTURAL),
-        extraArgs: Map<String, Any> = emptyMap()
-    ): List<FlickerTest> {
-        return supportedNavigationModes.flatMap { navBarMode ->
-            supportedRotations.map { rotation ->
-                createFlickerTest(navBarMode, rotation, rotation, extraArgs)
-            }
-        }
-    }
-
-    /**
-     * Gets a list of test configurations.
-     *
-     * Each configuration has a start and end orientation.
-     */
-    @JvmOverloads
-    @JvmStatic
-    fun rotationTests(
-        supportedRotations: List<PlatformConsts.Rotation> =
-            listOf(PlatformConsts.Rotation.ROTATION_0, PlatformConsts.Rotation.ROTATION_90),
-        supportedNavigationModes: List<PlatformConsts.NavBar> =
-            listOf(PlatformConsts.NavBar.MODE_3BUTTON, PlatformConsts.NavBar.MODE_GESTURAL),
-        extraArgs: Map<String, Any> = emptyMap()
-    ): List<FlickerTest> {
-        return supportedNavigationModes.flatMap { navBarMode ->
-            supportedRotations
-                .flatMap { start -> supportedRotations.map { end -> start to end } }
-                .filter { (start, end) -> start != end }
-                .map { (start, end) -> createFlickerTest(navBarMode, start, end, extraArgs) }
-        }
-    }
-
-    private fun createFlickerTest(
-        navBarMode: PlatformConsts.NavBar,
-        startRotation: PlatformConsts.Rotation,
-        endRotation: PlatformConsts.Rotation,
-        extraArgs: Map<String, Any>
-    ) =
-        FlickerTest(
-            ScenarioBuilder()
-                .withStartRotation(startRotation)
-                .withEndRotation(endRotation)
-                .withNavBarMode(navBarMode)
-                .withExtraConfigs(extraArgs)
-        )
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/IFlickerTestData.kt b/libraries/flicker/src/com/android/server/wm/flicker/IFlickerTestData.kt
deleted file mode 100644
index db6389d..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/IFlickerTestData.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.flicker
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import java.io.File
-
-interface IFlickerTestData {
-    /** Instrumentation to run the tests */
-    val instrumentation: Instrumentation
-    /** Test automation component used to interact with the device */
-    val device: UiDevice
-    /** Output directory for test results */
-    val outputDir: File
-    /** Enabled tracing monitors */
-    val traceMonitors: List<ITransitionMonitor>
-    /** Commands to be executed before the transition */
-    val transitionSetup: List<IFlickerTestData.() -> Any>
-    /** Test commands */
-    val transitions: List<IFlickerTestData.() -> Any>
-    /** Commands to be executed after the transition */
-    val transitionTeardown: List<IFlickerTestData.() -> Any>
-    /** Helper object for WM Synchronization */
-    val wmHelper: WindowManagerStateHelper
-
-    fun setAssertionsCheckedCallback(callback: (Boolean) -> Unit)
-
-    fun setCreateTagListener(callback: (String) -> Unit)
-
-    fun clearTagListener()
-
-    /**
-     * Runs a set of commands and, at the end, creates a tag containing the device state
-     *
-     * @param tag Identifier for the tag to be created
-     * @param commands Commands to execute before creating the tag
-     * @throws IllegalArgumentException If [tag] cannot be converted to a valid filename
-     */
-    fun withTag(tag: String, commands: IFlickerTestData.() -> Any)
-
-    fun createTag(tag: String)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TraceConfig.kt b/libraries/flicker/src/com/android/server/wm/flicker/TraceConfig.kt
deleted file mode 100644
index c3420c4..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/TraceConfig.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.flicker
-
-data class TraceConfigs(
-    val wmTrace: TraceConfig,
-    val layersTrace: TraceConfig,
-    val transitionsTrace: TraceConfig,
-    val transactionsTrace: TraceConfig
-) {
-    fun applyToAll(function: (TraceConfig) -> Unit) {
-        function(wmTrace)
-        function(layersTrace)
-        function(transitionsTrace)
-        function(transactionsTrace)
-    }
-}
-
-data class TraceConfig(
-    var required: Boolean,
-    var allowNoChange: Boolean,
-    var usingExistingTraces: Boolean
-)
-
-val DEFAULT_TRACE_CONFIG =
-    TraceConfigs(
-        wmTrace = TraceConfig(required = true, allowNoChange = false, usingExistingTraces = false),
-        layersTrace =
-            TraceConfig(required = true, allowNoChange = false, usingExistingTraces = false),
-        transitionsTrace =
-            TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
-        transactionsTrace =
-            TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false)
-    )
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/Utils.kt b/libraries/flicker/src/com/android/server/wm/flicker/Utils.kt
deleted file mode 100644
index deb1c97..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/Utils.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.flicker
-
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.traces.common.MILLISECOND_AS_NANOSECONDS
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.io.RunStatus
-import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.TimeZone
-
-object Utils {
-    private fun renameFile(src: File, dst: File) {
-        SystemUtil.runShellCommand("mv $src $dst")
-    }
-
-    private fun copyFile(src: File, dst: File) {
-        SystemUtil.runShellCommand("cp $src $dst")
-        SystemUtil.runShellCommand("chmod a+r $dst")
-    }
-
-    fun moveFile(src: File, dst: File) {
-        // Move the  file to the output directory
-        // Note: Due to b/141386109, certain devices do not allow moving the files between
-        //       directories with different encryption policies, so manually copy and then
-        //       remove the original file
-        //       Moreover, the copied trace file may end up with different permissions, resulting
-        //       in b/162072200, to prevent this, ensure the files are readable after copying
-        copyFile(src, dst)
-        SystemUtil.runShellCommand("rm $src")
-    }
-
-    fun addStatusToFileName(traceFile: File, status: RunStatus) {
-        val newFileName = "${status.prefix}_${traceFile.name}"
-        val dst = traceFile.resolveSibling(newFileName)
-        renameFile(traceFile, dst)
-    }
-
-    fun componentMatcherParamsFromName(name: String): Pair<String, String> {
-        var packageName = ""
-        var className = ""
-        if (name.contains("/")) {
-            if (name.contains("#")) {
-                name.removeSuffix("#")
-            }
-            val splitString = name.split('/')
-            packageName = splitString[0]
-            className = splitString[1]
-        } else {
-            className = name
-        }
-        return Pair(packageName, className)
-    }
-
-    fun componentNameMatcherHardcoded(str: String): ComponentNameMatcher? {
-        return when (true) {
-            str.contains("NavigationBar0") -> ComponentNameMatcher.NAV_BAR
-            str.contains("Taskbar") -> ComponentNameMatcher.TASK_BAR
-            str.contains("StatusBar") -> ComponentNameMatcher.STATUS_BAR
-            str.contains("RotationLayer") -> ComponentNameMatcher.ROTATION
-            str.contains("BackColorSurface") -> ComponentNameMatcher.BACK_SURFACE
-            str.contains("InputMethod") -> ComponentNameMatcher.IME
-            str.contains("IME-snapshot-surface") -> ComponentNameMatcher.IME_SNAPSHOT
-            str.contains("Splash Screen") -> ComponentNameMatcher.SPLASH_SCREEN
-            str.contains("SnapshotStartingWindow") -> ComponentNameMatcher.SNAPSHOT
-            str.contains("Letterbox") -> ComponentNameMatcher.LETTERBOX
-            str.contains("Wallpaper BBQ wrapper") -> ComponentNameMatcher.WALLPAPER_BBQ_WRAPPER
-            str.contains("PipContentOverlay") -> ComponentNameMatcher.PIP_CONTENT_OVERLAY
-            str.contains("com.google.android.apps.nexuslauncher") -> ComponentNameMatcher.LAUNCHER
-            str.contains("StageCoordinatorSplitDivider") -> ComponentNameMatcher.SPLIT_DIVIDER
-            else -> null
-        }
-    }
-
-    /**
-     * Obtains the component name matcher corresponding to a name (str) Returns null if the name is
-     * not found in the hardcoded list, and it does not contain both the package and class name
-     * (with a / separator)
-     */
-    fun componentNameMatcherFromName(
-        str: String,
-    ): ComponentNameMatcher? {
-        return try {
-            componentNameMatcherHardcoded(str)
-                ?: ComponentNameMatcher.unflattenFromStringWithJunk(str)
-        } catch (err: IllegalStateException) {
-            null
-        }
-    }
-
-    fun componentNameMatcherToString(componentNameMatcher: ComponentNameMatcher): String {
-        return "ComponentNameMatcher(\"${componentNameMatcher.packageName}\", " +
-            "\"${componentNameMatcher.className}\")"
-    }
-
-    fun componentNameMatcherToStringSimplified(componentNameMatcher: ComponentNameMatcher): String {
-        var className = componentNameMatcher.className
-        val separatedByDots = className.split('.')
-        if (separatedByDots.isNotEmpty()) {
-            className = separatedByDots[separatedByDots.size - 1]
-        }
-        className = className.replace(' ', '_')
-        return className
-    }
-
-    fun componentNameMatcherAsStringFromName(str: String): String? {
-        val componentMatcher = componentNameMatcherFromName(str)
-        return componentMatcher?.componentNameMatcherToString()
-    }
-
-    fun formatRealTimestamp(timestampNs: Long): String {
-        val timestampMs = timestampNs / MILLISECOND_AS_NANOSECONDS
-        val remainderNs = timestampNs % MILLISECOND_AS_NANOSECONDS
-        val date = Date(timestampMs)
-
-        val timeFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH)
-        timeFormatter.timeZone = TimeZone.getTimeZone("UTC")
-
-        return "${timeFormatter.format(date)}${remainderNs.toString().padStart(6, '0')}"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/annotation/FlickerServiceCompatible.kt b/libraries/flicker/src/com/android/server/wm/flicker/annotation/FlickerServiceCompatible.kt
deleted file mode 100644
index c602395..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/annotation/FlickerServiceCompatible.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.flicker.annotation
-
-/**
- * Annotate your Flicker test class with this annotation to enable Flicker as a Service on the
- * transition defined in the Flicker test class. It requires shell transitions to be enabled.
- */
-@Target(AnnotationTarget.CLASS)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class FlickerServiceCompatible
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunner.kt
deleted file mode 100644
index 3f6d445..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunner.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.io.ResultReaderWithLru
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.io.RunStatus
-
-/**
- * Helper class to run an assertion on a flicker artifact
- *
- * @param result flicker artifact data
- * @param resultReader helper class to read the flicker artifact
- * @param subjectsParser helper class to convert a result into flicker subjects
- */
-class ArtifactAssertionRunner(
-    private val result: IResultData,
-    resultReader: IReader = ResultReaderWithLru(result, DEFAULT_TRACE_CONFIG),
-    subjectsParser: SubjectsParser = SubjectsParser(resultReader)
-) : BaseAssertionRunner(resultReader, subjectsParser) {
-    override fun doUpdateStatus(newStatus: RunStatus) {
-        result.updateStatus(newStatus)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionDataFactory.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionDataFactory.kt
deleted file mode 100644
index 4b4d115..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionDataFactory.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-import kotlin.reflect.KClass
-
-/**
- * Helper class to create assertions to execute on a trace or state
- *
- * @param stateSubject Type of subject used for state assertions
- * @param traceSubject Type of subject used for trace assertions
- */
-class AssertionDataFactory(
-    stateSubject: KClass<out FlickerSubject>,
-    private val traceSubject: KClass<out FlickerTraceSubject<*>>
-) : AssertionStateDataFactory(stateSubject) {
-
-    /**
-     * Creates an [assertion] to be executed on trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun createTraceAssertion(
-        assertion: (FlickerTraceSubject<FlickerSubject>) -> Unit
-    ): AssertionData {
-        val closedAssertion: FlickerTraceSubject<FlickerSubject>.() -> Unit = {
-            require(!hasAssertions()) { "Subject was already used to execute assertions" }
-            assertion(this)
-            forAllEntries()
-        }
-        return AssertionData(
-            tag = AssertionTag.ALL,
-            expectedSubjectClass = traceSubject,
-            assertion = closedAssertion as FlickerSubject.() -> Unit
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactory.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactory.kt
deleted file mode 100644
index 3e0d4bf..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactory.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import kotlin.reflect.KClass
-
-/**
- * Helper class to create assertions to execute on a state
- *
- * @param stateSubject Type of subject used for state assertions
- */
-open class AssertionStateDataFactory(private val stateSubject: KClass<out FlickerSubject>) {
-    /**
-     * Creates an [assertion] to be executed on the initial state of a trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun createStartStateAssertion(assertion: FlickerSubject.() -> Unit) =
-        AssertionData(
-            tag = AssertionTag.START,
-            expectedSubjectClass = stateSubject,
-            assertion = assertion
-        )
-
-    /**
-     * Creates an [assertion] to be executed on the final state of a trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun createEndStateAssertion(assertion: FlickerSubject.() -> Unit) =
-        AssertionData(
-            tag = AssertionTag.END,
-            expectedSubjectClass = stateSubject,
-            assertion = assertion
-        )
-
-    /**
-     * Creates an [assertion] to be executed on a user defined moment ([tag]) of a trace
-     *
-     * @param assertion Assertion predicate
-     */
-    fun createTagAssertion(tag: String, assertion: FlickerSubject.() -> Unit) =
-        AssertionData(tag = tag, expectedSubjectClass = stateSubject, assertion = assertion)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/BaseAssertionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/BaseAssertionRunner.kt
deleted file mode 100644
index c0a85f7..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/BaseAssertionRunner.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.assertions
-
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.io.RunStatus
-
-/**
- * Helper class to run an assertions
- *
- * @param resultReader helper class to read the flicker artifact
- * @param subjectsParser helper class to convert a result into flicker subjects
- */
-abstract class BaseAssertionRunner(
-    private val resultReader: IReader,
-    private val subjectsParser: SubjectsParser = SubjectsParser(resultReader)
-) {
-    /**
-     * Executes [assertion] on the subjects parsed by [subjectsParser] and update its execution
-     * status
-     *
-     * @param assertion to run
-     *
-     * @return the transition execution error (if any) , assertion error (if any), null otherwise
-     */
-    fun runAssertion(assertion: AssertionData): Throwable? {
-        return resultReader.executionError ?: doRunAssertion(assertion)
-    }
-
-    private fun doRunAssertion(assertion: AssertionData): Throwable? {
-        return try {
-            assertion.checkAssertion(subjectsParser)
-            updateResultStatus(error = null)
-            null
-        } catch (error: Throwable) {
-            updateResultStatus(error)
-            FlickerAssertionErrorBuilder()
-                .fromError(error)
-                .atTag(assertion.tag)
-                .withReader(resultReader)
-                .build()
-        }
-    }
-
-    private fun updateResultStatus(error: Throwable?) {
-        val newStatus =
-            if (error == null) RunStatus.ASSERTION_SUCCESS else RunStatus.ASSERTION_FAILED
-
-        if (resultReader.isFailure || resultReader.runStatus == newStatus) return
-
-        doUpdateStatus(newStatus)
-    }
-
-    protected abstract fun doUpdateStatus(newStatus: RunStatus)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt b/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt
deleted file mode 100644
index 1f97326..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/assertions/FlickerAssertionErrorBuilder.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.assertions
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.assertions.FlickerAssertionError
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import java.io.ByteArrayOutputStream
-import java.io.PrintStream
-
-class FlickerAssertionErrorBuilder {
-    private var error: Throwable? = null
-    private var artifactPath: String = ""
-    private var tag = ""
-
-    fun fromError(error: Throwable): FlickerAssertionErrorBuilder = apply { this.error = error }
-
-    fun withReader(reader: IReader): FlickerAssertionErrorBuilder = apply {
-        artifactPath = reader.artifactPath
-    }
-
-    fun atTag(_tag: String): FlickerAssertionErrorBuilder = apply {
-        tag =
-            when (_tag) {
-                AssertionTag.START -> "before transition (initial state)"
-                AssertionTag.END -> "after transition (final state)"
-                AssertionTag.ALL -> "during transition"
-                else -> "at user-defined location ($_tag)"
-            }
-    }
-
-    fun build(): FlickerAssertionError {
-        return FlickerAssertionError(errorMessage, rootCause)
-    }
-
-    private val errorMessage
-        get() = buildString {
-            val error = error
-            requireNotNull(error)
-            if (error is FlickerSubjectException) {
-                appendLine(error.message)
-                appendLine()
-                append("\t").appendLine(Fact("Location", tag))
-                appendLine()
-            } else {
-                appendLine(error.message)
-            }
-            append("Trace file:").append(traceFileMessage)
-            appendLine()
-            appendLine("Cause:")
-            append(rootCauseStackTrace)
-            appendLine()
-            appendLine("Full stacktrace:")
-            appendLine()
-        }
-
-    private val traceFileMessage
-        get() = buildString {
-            if (artifactPath.isNotEmpty()) {
-                append("\t")
-                append(artifactPath)
-            }
-        }
-
-    private val rootCauseStackTrace: String
-        get() {
-            val rootCause = rootCause
-            return if (rootCause != null) {
-                val baos = ByteArrayOutputStream()
-                PrintStream(baos, true).use { ps -> rootCause.printStackTrace(ps) }
-                "\t$baos"
-            } else {
-                ""
-            }
-        }
-
-    /**
-     * In some paths the exceptions are encapsulated by the Truth subjects To make sure the correct
-     * error is printed, located the first non-subject related exception and use that as cause.
-     */
-    private val rootCause: Throwable?
-        get() {
-            var childCause: Throwable? = this.error?.cause
-            if (childCause != null && childCause is FlickerSubjectException) {
-                childCause = childCause.cause
-            }
-            return childCause
-        }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedAssertionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedAssertionRunner.kt
deleted file mode 100644
index 30d8f8f..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedAssertionRunner.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.assertions.BaseAssertionRunner
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.io.RunStatus
-
-/**
- * Helper class to run an assertion on a flicker artifact from a [DataStore]
- *
- * @param scenario flicker scenario existing in the [DataStore]
- * @param resultReader helper class to read the flicker artifact
- * @param subjectsParser helper class to convert a result into flicker subjects
- */
-class CachedAssertionRunner(
-    private val scenario: IScenario,
-    resultReader: CachedResultReader = CachedResultReader(scenario, DEFAULT_TRACE_CONFIG),
-    subjectsParser: SubjectsParser = SubjectsParser(resultReader)
-) : BaseAssertionRunner(resultReader, subjectsParser) {
-    override fun doUpdateStatus(newStatus: RunStatus) {
-        val result = DataStore.getResult(scenario)
-        result.updateStatus(newStatus)
-        DataStore.replaceResult(scenario, result)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultReader.kt b/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultReader.kt
deleted file mode 100644
index 56437d2..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultReader.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import com.android.server.wm.flicker.TraceConfigs
-import com.android.server.wm.flicker.io.ResultReaderWithLru
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.io.IReader
-
-/** Helper class to read results of a [scenario] from the [DataStore] */
-class CachedResultReader(
-    private val scenario: IScenario,
-    traceConfig: TraceConfigs,
-    private val reader: IReader = ResultReaderWithLru(DataStore.getResult(scenario), traceConfig)
-) : IReader by reader {
-    override fun toString(): String = "$scenario ($reader)"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultWriter.kt b/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultWriter.kt
deleted file mode 100644
index 75affde..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/datastore/CachedResultWriter.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.io.ResultWriter
-
-/** Result writer that adds data of a [scenario] to the [DataStore] */
-class CachedResultWriter : ResultWriter() {
-    override fun write(): IResultData {
-        val result = super.write()
-        DataStore.addResult(scenario, result)
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/datastore/DataStore.kt b/libraries/flicker/src/com/android/server/wm/flicker/datastore/DataStore.kt
deleted file mode 100644
index 2df21db..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/datastore/DataStore.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import androidx.annotation.VisibleForTesting
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-
-/** In memory data store for flicker transitions, assertions and results */
-object DataStore {
-    private val cachedResults = mutableMapOf<IScenario, IResultData>()
-    private val cachedFlickerServiceAssertions = mutableMapOf<IScenario, List<IAssertionResult>>()
-
-    @VisibleForTesting
-    fun clear() {
-        cachedResults.clear()
-        cachedFlickerServiceAssertions.clear()
-    }
-
-    /** @return if the store has results for [scenario] */
-    fun containsResult(scenario: IScenario): Boolean = cachedResults.containsKey(scenario)
-
-    /**
-     * Adds [result] to the store with [scenario] as id
-     *
-     * @throws IllegalStateException is [scenario] already exists in the data store
-     */
-    @Throws(IllegalStateException::class)
-    fun addResult(scenario: IScenario, result: IResultData) {
-        if (containsResult(scenario)) {
-            error("Result for $scenario already in data store")
-        }
-        cachedResults[scenario] = result
-    }
-
-    /**
-     * Replaces the old value [scenario] result in the store by [newResult]
-     *
-     * @throws IllegalStateException is [scenario] doesn't exist in the data store
-     */
-    @Throws(IllegalStateException::class)
-    fun replaceResult(scenario: IScenario, newResult: IResultData) {
-        if (!containsResult(scenario)) {
-            error("Result for $scenario not in data store")
-        }
-        cachedResults[scenario] = newResult
-    }
-
-    /**
-     * @return the result for [scenario]
-     *
-     * @throws IllegalStateException is [scenario] doesn't exist in the data store
-     */
-    @Throws(IllegalStateException::class)
-    fun getResult(scenario: IScenario): IResultData =
-        cachedResults[scenario] ?: error("No value for $scenario")
-
-    /** @return if the store has results for [scenario] */
-    fun containsFlickerServiceResult(scenario: IScenario): Boolean =
-        cachedFlickerServiceAssertions.containsKey(scenario)
-
-    fun addFlickerServiceResults(scenario: IScenario, results: List<IAssertionResult>) {
-        if (containsFlickerServiceResult(scenario)) {
-            error("Result for $scenario already in data store")
-        }
-        cachedFlickerServiceAssertions[scenario] = results
-    }
-
-    fun getFlickerServiceResults(scenario: IScenario): List<IAssertionResult> {
-        return cachedFlickerServiceAssertions[scenario]
-            ?: error("No flicker service results for $scenario")
-    }
-
-    fun getFlickerServiceResultsForAssertion(
-        scenario: IScenario,
-        assertionName: String
-    ): List<IAssertionResult> {
-        return cachedFlickerServiceAssertions[scenario]?.filter {
-            it.assertion.name == assertionName
-        }
-            ?: error("Assertion with name $assertionName not found for scenario $scenario")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt
deleted file mode 100644
index fdb64a1..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/AutomationUtils.kt
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.helpers
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.graphics.Point
-import android.graphics.Rect
-import android.os.RemoteException
-import android.os.SystemClock
-import android.util.Rational
-import android.view.View
-import android.view.ViewConfiguration
-import androidx.annotation.VisibleForTesting
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.Configurator
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.helpers.WindowUtils.displayBounds
-import com.android.server.wm.flicker.helpers.WindowUtils.estimateNavigationBarPosition
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.parser.toAndroidRect
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.Assert
-import org.junit.Assert.assertNotNull
-
-const val FIND_TIMEOUT: Long = 10000
-const val FAST_WAIT_TIMEOUT: Long = 0
-val DOCKED_STACK_DIVIDER = ComponentNameMatcher("", "DockedStackDivider")
-const val IME_PACKAGE = "com.google.android.inputmethod.latin"
-@VisibleForTesting const val SYSTEMUI_PACKAGE = "com.android.systemui"
-private val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L
-private const val TAG = "FLICKER"
-
-/**
- * Sets [android.app.UiAutomation.waitForIdle] global timeout to 0 causing the
- * [android.app.UiAutomation.waitForIdle] function to timeout instantly. This removes some delays
- * when using the UIAutomator library required to create fast UI transitions.
- */
-fun setFastWait() {
-    Configurator.getInstance().waitForIdleTimeout = FAST_WAIT_TIMEOUT
-}
-
-/** Reverts [android.app.UiAutomation.waitForIdle] to default behavior. */
-fun setDefaultWait() {
-    Configurator.getInstance().waitForIdleTimeout = FIND_TIMEOUT
-}
-
-/** Checks if the device is running on gestural or 2-button navigation modes */
-fun UiDevice.isQuickstepEnabled(): Boolean {
-    val enabled = this.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null
-    CrossPlatform.log.d(TAG, "Quickstep enabled: $enabled")
-    return enabled
-}
-
-/** Checks if the display is rotated or not */
-fun UiDevice.isRotated(): Boolean {
-    return PlatformConsts.Rotation.getByValue(this.displayRotation).isRotated()
-}
-
-/** Reopens the first device window from the list of recent apps (overview) */
-fun UiDevice.reopenAppFromOverview(wmHelper: WindowManagerStateHelper) {
-    val x = this.displayWidth / 2
-    val y = this.displayHeight / 2
-    this.click(x, y)
-
-    wmHelper.StateSyncBuilder().withAppTransitionIdle().waitFor()
-}
-
-/**
- * Shows quickstep
- *
- * @throws AssertionError When quickstep does not appear
- */
-fun UiDevice.openQuickstep(wmHelper: WindowManagerStateHelper) {
-    if (this.isQuickstepEnabled()) {
-        val navBar = this.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"))
-
-        // TODO(vishnun) investigate why this object cannot be found.
-        val navBarVisibleBounds: Rect =
-            if (navBar != null) {
-                navBar.visibleBounds
-            } else {
-                CrossPlatform.log.e(TAG, "Could not find nav bar, infer location")
-                estimateNavigationBarPosition(PlatformConsts.Rotation.ROTATION_0)
-                    .bounds
-                    .toAndroidRect()
-            }
-
-        val startX = navBarVisibleBounds.centerX()
-        val startY = navBarVisibleBounds.centerY()
-        val endX: Int
-        val endY: Int
-        val height: Int
-        val steps: Int
-        if (this.isRotated()) {
-            height = this.displayWidth
-            endX = height * 2 / 3
-            endY = navBarVisibleBounds.centerY()
-            steps = (endX - startX) / 100 // 100 px/step
-        } else {
-            height = this.displayHeight
-            endX = navBarVisibleBounds.centerX()
-            endY = height * 2 / 3
-            steps = (startY - endY) / 100 // 100 px/step
-        }
-        // Swipe from nav bar to 2/3rd down the screen.
-        this.swipe(startX, startY, endX, endY, steps)
-    }
-
-    // use a long timeout to wait until recents populated
-    val recentsSysUISelector = By.res(this.launcherPackageName, "overview_panel")
-    var recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
-
-    // Quickstep detection is flaky on AOSP, UIDevice doesn't always find SysUI elements
-    // If it couldn't find, try pressing 'recent items' button
-    if (recents == null) {
-        try {
-            this.pressRecentApps()
-        } catch (e: RemoteException) {
-            throw RuntimeException(e)
-        }
-        recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
-    }
-    assertNotNull("Recent items didn't appear", recents)
-    wmHelper
-        .StateSyncBuilder()
-        .withNavOrTaskBarVisible()
-        .withStatusBarVisible()
-        .withAppTransitionIdle()
-        .waitForAndVerify()
-}
-
-private fun getLauncherOverviewSelector(device: UiDevice): BySelector {
-    return By.res(device.launcherPackageName, "overview_panel")
-}
-
-private fun longPressRecents(device: UiDevice) {
-    val recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps")
-    val recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find 'recent items' button", recentsButton)
-    recentsButton.click(LONG_PRESS_TIMEOUT)
-}
-
-/** Wait for any IME view to appear */
-fun UiDevice.waitForIME(): Boolean {
-    val ime = this.wait(Until.findObject(By.pkg(IME_PACKAGE)), FIND_TIMEOUT)
-    return ime != null
-}
-
-private fun openQuickStepAndLongPressOverviewIcon(
-    device: UiDevice,
-    wmHelper: WindowManagerStateHelper
-) {
-    if (device.isQuickstepEnabled()) {
-        device.openQuickstep(wmHelper)
-    } else {
-        try {
-            device.pressRecentApps()
-        } catch (e: RemoteException) {
-            CrossPlatform.log.e(TAG, "launchSplitScreen", e)
-        }
-    }
-    val overviewIconSelector = By.res(device.launcherPackageName, "icon").clazz(View::class.java)
-    val overviewIcon = device.wait(Until.findObject(overviewIconSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find app icon in Overview", overviewIcon)
-    overviewIcon.click()
-}
-
-fun UiDevice.openQuickStepAndClearRecentAppsFromOverview(wmHelper: WindowManagerStateHelper) {
-    if (this.isQuickstepEnabled()) {
-        this.openQuickstep(wmHelper)
-    } else {
-        try {
-            this.pressRecentApps()
-        } catch (e: RemoteException) {
-            CrossPlatform.log.e(TAG, "launchSplitScreen", e)
-        }
-    }
-    for (i in 0..9) {
-        this.swipe(
-            this.displayWidth / 2,
-            this.displayHeight / 2,
-            this.displayWidth,
-            this.displayHeight / 2,
-            5
-        )
-        // If "Clear all"  button appears, use it
-        val clearAllSelector = By.res(this.launcherPackageName, "clear_all")
-        wait(Until.findObject(clearAllSelector), FAST_WAIT_TIMEOUT)?.click()
-    }
-    this.pressHome()
-}
-
-/**
- * Opens quick step and puts the first app from the list of recently used apps into split-screen
- *
- * @throws AssertionError when unable to open the list of recently used apps, or when it does not
- * contain a button to enter split screen mode
- */
-fun UiDevice.launchSplitScreen(wmHelper: WindowManagerStateHelper) {
-    openQuickStepAndLongPressOverviewIcon(this, wmHelper)
-    val splitScreenButtonSelector = By.text("Split screen")
-    val splitScreenButton = this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find Split screen button in Overview", splitScreenButton)
-    splitScreenButton.click()
-
-    // Wait for animation to complete.
-    this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
-    wmHelper
-        .StateSyncBuilder()
-        .add(WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER))
-        .withAppTransitionIdle()
-        .waitForAndVerify()
-
-    if (!this.isInSplitScreen()) {
-        Assert.fail("Unable to find Split screen divider")
-    }
-}
-
-/** Checks if the recent application is able to split screen(resizeable) */
-fun UiDevice.canSplitScreen(wmHelper: WindowManagerStateHelper): Boolean {
-    openQuickStepAndLongPressOverviewIcon(this, wmHelper)
-    val splitScreenButtonSelector = By.text("Split screen")
-    val canSplitScreen =
-        this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT) != null
-    this.pressHome()
-    return canSplitScreen
-}
-
-/** Checks if the device is in split screen by searching for the split screen divider */
-fun UiDevice.isInSplitScreen(): Boolean {
-    return this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT) != null
-}
-
-fun waitSplitScreenGone(wmHelper: WindowManagerStateHelper) {
-    return wmHelper
-        .StateSyncBuilder()
-        .add(WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER).negate())
-        .withAppTransitionIdle()
-        .waitForAndVerify()
-}
-
-private val splitScreenDividerSelector: BySelector
-    get() = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle")
-
-/**
- * Drags the split screen divider to the top of the screen to close it
- *
- * @throws AssertionError when unable to find the split screen divider
- */
-fun UiDevice.exitSplitScreen() {
-    // Quickstep enabled
-    val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find Split screen divider", divider)
-
-    // Drag the split screen divider to the top of the screen
-    val dstPoint =
-        if (this.isRotated()) {
-            Point(0, this.displayWidth / 2)
-        } else {
-            Point(this.displayWidth / 2, 0)
-        }
-    divider.drag(dstPoint, 400)
-    // Wait for animation to complete.
-    SystemClock.sleep(2000)
-}
-
-/**
- * Drags the split screen divider to the bottom of the screen to close it
- *
- * @throws AssertionError when unable to find the split screen divider
- */
-fun UiDevice.exitSplitScreenFromBottom(wmHelper: WindowManagerStateHelper) {
-    // Quickstep enabled
-    val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find Split screen divider", divider)
-
-    // Drag the split screen divider to the bottom of the screen
-    val dstPoint =
-        if (this.isRotated()) {
-            Point(this.displayWidth, this.displayWidth / 2)
-        } else {
-            Point(this.displayWidth / 2, this.displayHeight)
-        }
-    divider.drag(dstPoint, 400)
-    waitSplitScreenGone(wmHelper)
-}
-
-/**
- * Drags the split screen divider to resize the windows in split screen
- *
- * @throws AssertionError when unable to find the split screen divider
- */
-fun UiDevice.resizeSplitScreen(windowHeightRatio: Rational) {
-    val dividerSelector = splitScreenDividerSelector
-    val divider = this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
-    assertNotNull("Unable to find Split screen divider", divider)
-    val destHeight = (displayBounds.height() * windowHeightRatio.toFloat()).toInt()
-
-    // Drag the split screen divider to so that the ratio of top window height and bottom
-    // window height is windowHeightRatio
-    this.drag(
-        divider.visibleBounds.centerX(),
-        divider.visibleBounds.centerY(),
-        this.displayWidth / 2,
-        destHeight,
-        10
-    )
-    this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
-    // Wait for animation to complete.
-    SystemClock.sleep(2000)
-}
-
-/** Checks if the device has a window with the package name */
-fun UiDevice.hasWindow(packageName: String): Boolean {
-    return this.wait(Until.findObject(By.pkg(packageName)), FIND_TIMEOUT) != null
-}
-
-/** Waits until the package with that name is gone */
-fun UiDevice.waitUntilGone(packageName: String): Boolean {
-    return this.wait(Until.gone(By.pkg(packageName)), FIND_TIMEOUT) != null
-}
-
-fun stopPackage(context: Context, packageName: String) {
-    SystemUtil.runShellCommand("am force-stop $packageName")
-    val packageUid =
-        try {
-            context.packageManager.getPackageUid(packageName, /* flags */ 0)
-        } catch (e: PackageManager.NameNotFoundException) {
-            return
-        }
-    while (targetPackageIsRunning(packageUid)) {
-        try {
-            Thread.sleep(100)
-        } catch (e: InterruptedException) { // ignore
-        }
-    }
-}
-
-private fun targetPackageIsRunning(uid: Int): Boolean {
-    val result = SystemUtil.runShellCommand("cmd activity get-uid-state $uid")
-    return !result.contains("(NONEXISTENT)")
-}
-
-/** Turns on the device display and presses the home button to reach the launcher screen */
-fun UiDevice.wakeUpAndGoToHomeScreen() {
-    try {
-        this.wakeUp()
-    } catch (e: RemoteException) {
-        throw RuntimeException(e)
-    }
-    this.pressHome()
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/BrowserAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/BrowserAppHelper.kt
deleted file mode 100644
index d8e5265..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/BrowserAppHelper.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.net.Uri
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/**
- * Helper to launch the default browser app (compatible with AOSP)
- *
- * This helper has no other functionality but the app launch.
- *
- * This helper is used to launch an app after some operations (e.g., navigation mode change), so
- * that the device is stable before executing flicker tests
- */
-class BrowserAppHelper(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) :
-    StandardAppHelper(
-        instrumentation,
-        getBrowserName(pkgManager),
-        getBrowserComponent(pkgManager)
-    ) {
-    override fun getOpenAppIntent(): Intent =
-        pkgManager.getLaunchIntentForPackage(packageName)
-            ?: error("Unable to find intent for browser")
-
-    companion object {
-        private fun getBrowserIntent(): Intent {
-            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            return intent
-        }
-
-        private fun getBrowserName(pkgManager: PackageManager): String {
-            val intent = getBrowserIntent()
-            val resolveInfo =
-                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                    ?: error("Unable to resolve browser activity")
-
-            return resolveInfo.loadLabel(pkgManager).toString()
-        }
-
-        private fun getBrowserComponent(pkgManager: PackageManager): ComponentNameMatcher {
-            val intent = getBrowserIntent()
-            val resolveInfo =
-                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                    ?: error("Unable to resolve browser activity")
-            return ComponentNameMatcher(resolveInfo.activityInfo.packageName, className = "")
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalculatorAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalculatorAppHelper.kt
deleted file mode 100644
index 80afdb7..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalculatorAppHelper.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/**
- * Helper to launch the calculator app (not compatible with AOSP)
- *
- * This helper has no other functionality but the app launch.
- */
-class CalculatorAppHelper(instrumentation: Instrumentation) :
-    StandardAppHelper(
-        instrumentation,
-        "Calculator",
-        ComponentNameMatcher(
-            packageName = "com.google.android.calculator",
-            className = "com.android.calculator2.Calculator"
-        )
-    )
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalendarAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalendarAppHelper.kt
deleted file mode 100644
index ef73a76..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CalendarAppHelper.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.net.Uri
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/** Helper to launch the Calendar app. */
-class CalendarAppHelper
-@JvmOverloads
-constructor(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) :
-    StandardAppHelper(
-        instrumentation,
-        getCalendarLauncherName(pkgManager),
-        getCalendarComponent(pkgManager)
-    ) {
-    companion object {
-        private fun getCalendarIntent(): Intent {
-            val epochEventStartTime = 0
-            val uri = Uri.parse("content://com.android.calendar/time/" + epochEventStartTime)
-            val intent = Intent(Intent.ACTION_VIEW)
-            intent.data = uri
-            intent.putExtra("VIEW", "DAY")
-            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
-            return intent
-        }
-
-        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
-            val intent = getCalendarIntent()
-            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                ?: error("unable to resolve calendar activity")
-        }
-
-        private fun getCalendarComponent(pkgManager: PackageManager): ComponentNameMatcher {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return ComponentNameMatcher(
-                resolveInfo.activityInfo.packageName,
-                className = resolveInfo.activityInfo.name
-            )
-        }
-
-        private fun getCalendarLauncherName(pkgManager: PackageManager): String {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return resolveInfo.loadLabel(pkgManager).toString()
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
deleted file mode 100644
index 3c01963..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.provider.MediaStore
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/** Helper to launch the Camera app. */
-class CameraAppHelper
-@JvmOverloads
-constructor(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) :
-    StandardAppHelper(
-        instrumentation,
-        getCameraLauncherName(pkgManager),
-        getCameraComponent(pkgManager)
-    ) {
-
-    override fun getOpenAppIntent(): Intent =
-        pkgManager.getLaunchIntentForPackage(packageName)
-            ?: error("Unable to find intent for camera")
-
-    companion object {
-        private fun getCameraIntent(): Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
-
-        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo =
-            pkgManager.resolveActivity(getCameraIntent(), PackageManager.MATCH_DEFAULT_ONLY)
-                ?: error("unable to resolve camera activity")
-
-        private fun getCameraComponent(pkgManager: PackageManager): ComponentNameMatcher =
-            ComponentNameMatcher(
-                getResolveInfo(pkgManager).activityInfo.packageName,
-                className = ""
-            )
-
-        private fun getCameraLauncherName(pkgManager: PackageManager): String =
-            getResolveInfo(pkgManager).loadLabel(pkgManager).toString()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/FaasUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/FaasUtils.kt
deleted file mode 100644
index 4d32beb..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/FaasUtils.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.flicker.helpers
-
-const val IS_FAAS_ENABLED = true
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/GmailAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/GmailAppHelper.kt
deleted file mode 100644
index 7bcd7b4..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/GmailAppHelper.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/** Helper to launch the Gmail app (not compatible with AOSP) */
-class GmailAppHelper(instrumentation: Instrumentation) :
-    StandardAppHelper(
-        instrumentation,
-        "Gmail",
-        ComponentNameMatcher(
-            packageName = "com.google.android.gm",
-            className = "com.google.android.gm.ConversationListActivityGmail"
-        )
-    )
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/MapsAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/MapsAppHelper.kt
deleted file mode 100644
index 566d69d..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/MapsAppHelper.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.net.Uri
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/** Helper to launch the Maps app (not compatible with AOSP) */
-class MapsAppHelper
-@JvmOverloads
-constructor(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) :
-    StandardAppHelper(
-        instrumentation,
-        getMapLauncherName(pkgManager),
-        getMapComponent(pkgManager)
-    ) {
-    companion object {
-        private fun getMapIntent(): Intent {
-            val gmmIntentUri = Uri.parse("google.streetview:cbll=46.414382,10.013988")
-            return Intent(Intent.ACTION_VIEW, gmmIntentUri)
-        }
-
-        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
-            val intent = getMapIntent()
-            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                ?: error("unable to resolve camera activity")
-        }
-
-        private fun getMapComponent(pkgManager: PackageManager): ComponentNameMatcher {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return ComponentNameMatcher(
-                resolveInfo.activityInfo.packageName,
-                className = resolveInfo.activityInfo.name
-            )
-        }
-
-        private fun getMapLauncherName(pkgManager: PackageManager): String {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return resolveInfo.loadLabel(pkgManager).toString()
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/MessagingAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/MessagingAppHelper.kt
deleted file mode 100644
index 163d268..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/MessagingAppHelper.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.net.Uri
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/**
- * Helper to launch the default messaging app (compatible with AOSP)
- *
- * This helper has no other functionality but the app launch.
- */
-class MessagingAppHelper(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) : StandardAppHelper(instrumentation, "SampleApp", getMessagesComponent(pkgManager)) {
-    override fun getOpenAppIntent(): Intent =
-        pkgManager.getLaunchIntentForPackage(packageName)
-            ?: error("Unable to find intent for browser")
-
-    companion object {
-        private fun getMessagesIntent(): Intent {
-            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:"))
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            return intent
-        }
-
-        private fun getMessagesComponent(pkgManager: PackageManager): ComponentNameMatcher {
-            val intent = getMessagesIntent()
-            val resolveInfo =
-                pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                    ?: error("Unable to resolve browser activity")
-            return ComponentNameMatcher(resolveInfo.activityInfo.packageName, className = "")
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/RotationUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/RotationUtils.kt
deleted file mode 100644
index e7c191c..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/RotationUtils.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.flicker.helpers
-
-import android.graphics.Insets
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.service.PlatformConsts
-
-/**
- * A class containing utility methods related to rotation.
- *
- * @hide
- */
-object RotationUtils {
-    /** Rotates an Insets according to the given rotation. */
-    fun rotateInsets(insets: Insets?, rotation: PlatformConsts.Rotation): Insets {
-        if (insets == null || insets === Insets.NONE) {
-            return Insets.NONE
-        }
-        val rotated =
-            when (rotation) {
-                PlatformConsts.Rotation.ROTATION_0 -> insets
-                PlatformConsts.Rotation.ROTATION_90 ->
-                    Insets.of(insets.top, insets.right, insets.bottom, insets.left)
-                PlatformConsts.Rotation.ROTATION_180 ->
-                    Insets.of(insets.right, insets.bottom, insets.left, insets.top)
-                PlatformConsts.Rotation.ROTATION_270 ->
-                    Insets.of(insets.bottom, insets.left, insets.top, insets.right)
-            }
-        return rotated
-    }
-
-    /**
-     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated from
-     * oldRotation to newRotation. This assumes that parentBounds is at 0,0 and remains at 0,0 after
-     * rotation. The bounds will be at the same physical position in parentBounds.
-     */
-    fun rotateBounds(
-        inBounds: Rect,
-        parentBounds: Rect,
-        oldRotation: PlatformConsts.Rotation,
-        newRotation: PlatformConsts.Rotation
-    ): Rect = rotateBounds(inBounds, parentBounds, deltaRotation(oldRotation, newRotation))
-
-    /**
-     * Rotates inOutBounds together with the parent for a given rotation delta. This assumes that
-     * the parent starts at 0,0 and remains at 0,0 after the rotation. The inOutBounds will remain
-     * at the same physical position within the parent.
-     */
-    fun rotateBounds(
-        inBounds: Rect,
-        parentWidth: Int,
-        parentHeight: Int,
-        rotation: PlatformConsts.Rotation
-    ): Rect {
-        val origLeft = inBounds.left
-        val origTop = inBounds.top
-        return when (rotation) {
-            PlatformConsts.Rotation.ROTATION_0 -> inBounds
-            PlatformConsts.Rotation.ROTATION_90 ->
-                Rect.from(
-                    left = inBounds.top,
-                    top = parentWidth - inBounds.right,
-                    right = inBounds.bottom,
-                    bottom = parentWidth - origLeft
-                )
-            PlatformConsts.Rotation.ROTATION_180 ->
-                Rect.from(
-                    left = parentWidth - inBounds.right,
-                    right = parentWidth - origLeft,
-                    top = parentHeight - inBounds.bottom,
-                    bottom = parentHeight - origTop
-                )
-            PlatformConsts.Rotation.ROTATION_270 ->
-                Rect.from(
-                    left = parentHeight - inBounds.bottom,
-                    bottom = inBounds.right,
-                    right = parentHeight - inBounds.top,
-                    top = origLeft
-                )
-        }
-    }
-
-    /**
-     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
-     * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and remains
-     * at 0,0 after rotation. The bounds will be at the same physical position in parentBounds.
-     */
-    fun rotateBounds(inBounds: Rect, parentBounds: Rect, rotation: PlatformConsts.Rotation): Rect =
-        rotateBounds(inBounds, parentBounds.right, parentBounds.bottom, rotation)
-
-    /** @return the rotation needed to rotate from oldRotation to newRotation. */
-    fun deltaRotation(
-        oldRotation: PlatformConsts.Rotation,
-        newRotation: PlatformConsts.Rotation
-    ): PlatformConsts.Rotation {
-        var delta = newRotation.value - oldRotation.value
-        if (delta < 0) delta += 4
-        return PlatformConsts.Rotation.getByValue(delta)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/ShellTransitionUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/ShellTransitionUtils.kt
deleted file mode 100644
index 70f1e27..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/ShellTransitionUtils.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-@file:JvmName("ShellTransitionUtils")
-
-package com.android.server.wm.flicker.helpers
-
-import android.os.SystemProperties
-
-val isShellTransitionsEnabled = SystemProperties.getBoolean("persist.wm.debug.shell_transit", true)
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt
deleted file mode 100644
index 5fb7ffd..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/StandardAppHelper.kt
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.helpers
-
-import android.app.ActivityManager
-import android.app.Instrumentation
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.platform.helpers.AbstractStandardAppHelper
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.traces.common.Condition
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.DeviceStateDump
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentNameMatcher
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-
-/**
- * Class to take advantage of {@code IAppHelper} interface so the same test can be run against first
- * party and third party apps.
- */
-open class StandardAppHelper(
-    instr: Instrumentation,
-    val appName: String,
-    val componentMatcher: ComponentNameMatcher
-) : AbstractStandardAppHelper(instr), IComponentNameMatcher by componentMatcher {
-    constructor(
-        instr: Instrumentation,
-        appName: String,
-        packageName: String,
-        activity: String
-    ) : this(instr, appName, ComponentNameMatcher(packageName, ".$activity"))
-
-    protected val pkgManager: PackageManager = instr.context.packageManager
-
-    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
-
-    private val activityManager: ActivityManager?
-        get() = mInstrumentation.context.getSystemService(ActivityManager::class.java)
-
-    protected val context: Context
-        get() = mInstrumentation.context
-
-    override val packageName = componentMatcher.packageName
-
-    override val className = componentMatcher.className
-
-    protected val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
-
-    private fun getAppSelector(expectedPackageName: String): BySelector {
-        val expected = expectedPackageName.ifEmpty { packageName }
-        return By.pkg(expected).depth(0)
-    }
-
-    override fun open() {
-        open(`package`)
-    }
-
-    protected fun open(expectedPackageName: String) {
-        tapl.goHome().switchToAllApps().getAppIcon(launcherName).launch(expectedPackageName)
-    }
-
-    /** {@inheritDoc} */
-    override fun getPackage(): String {
-        return packageName
-    }
-
-    /** {@inheritDoc} */
-    override fun getOpenAppIntent(): Intent {
-        val intent = Intent()
-        intent.addCategory(Intent.CATEGORY_LAUNCHER)
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        intent.component = ComponentName(packageName, className)
-        return intent
-    }
-
-    /** {@inheritDoc} */
-    override fun getLauncherName(): String {
-        return appName
-    }
-
-    /** {@inheritDoc} */
-    override fun dismissInitialDialogs() {}
-
-    /** {@inheritDoc} */
-    override fun exit() {
-        CrossPlatform.log.withTracing("exit") {
-            // Ensure all testing components end up being closed.
-            activityManager?.forceStopPackage(packageName)
-        }
-    }
-
-    /** Exits the activity and wait for activity destroyed */
-    fun exit(wmHelper: WindowManagerStateHelper) {
-        CrossPlatform.log.withTracing("${this::class.simpleName}#exitAndWait") {
-            exit()
-            waitForActivityDestroyed(wmHelper)
-        }
-    }
-
-    /** Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} */
-    private fun waitForActivityDestroyed(wmHelper: WindowManagerStateHelper) {
-        val waitMsg =
-            "state of ${componentMatcher.toActivityIdentifier()} to be " +
-                WindowManagerState.STATE_DESTROYED
-        wmHelper
-            .StateSyncBuilder()
-            .add(waitMsg) {
-                !it.wmState.containsActivity(componentMatcher) ||
-                    it.wmState.hasActivityState(
-                        componentMatcher,
-                        WindowManagerState.STATE_DESTROYED
-                    )
-            }
-            .withAppTransitionIdle()
-            .waitForAndVerify()
-    }
-
-    private fun launchAppViaIntent(
-        action: String? = null,
-        stringExtras: Map<String, String> = mapOf()
-    ) {
-        CrossPlatform.log.withTracing("${this::class.simpleName}#launchAppViaIntent") {
-            val intent = openAppIntent
-            intent.action = action
-            stringExtras.forEach { intent.putExtra(it.key, it.value) }
-            context.startActivity(intent)
-        }
-    }
-
-    /**
-     * Launches the app through an intent instead of interacting with the launcher.
-     *
-     * Uses UiAutomation to detect when the app is open
-     */
-    @JvmOverloads
-    open fun launchViaIntent(
-        expectedPackageName: String = "",
-        action: String? = null,
-        stringExtras: Map<String, String> = mapOf()
-    ) {
-        launchAppViaIntent(action, stringExtras)
-        val appSelector = getAppSelector(expectedPackageName)
-        uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS)
-    }
-
-    /**
-     * Launches the app through an intent instead of interacting with the launcher and waits until
-     * the app window is visible
-     */
-    @JvmOverloads
-    open fun launchViaIntent(
-        wmHelper: WindowManagerStateHelper,
-        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
-        action: String? = null,
-        stringExtras: Map<String, String> = mapOf(),
-        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
-    ) =
-        launchViaIntentAndWaitShown(
-            wmHelper,
-            launchedAppComponentMatcherOverride,
-            action,
-            stringExtras,
-            waitConditions
-        )
-
-    /**
-     * Launches the app through an intent instead of interacting with the launcher and waits until
-     * the app window is visible
-     */
-    protected fun launchViaIntentAndWaitShown(
-        wmHelper: WindowManagerStateHelper,
-        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
-        action: String? = null,
-        stringExtras: Map<String, String> = mapOf(),
-        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
-    ) {
-        launchAppViaIntent(action, stringExtras)
-        doWaitShown(wmHelper, launchedAppComponentMatcherOverride, waitConditions)
-    }
-
-    private fun doWaitShown(
-        wmHelper: WindowManagerStateHelper,
-        launchedAppComponentMatcherOverride: IComponentMatcher? = null,
-        waitConditions: Array<Condition<DeviceStateDump>> = emptyArray()
-    ) {
-        CrossPlatform.log.withTracing("${this::class.simpleName}#doWaitShown") {
-            val expectedWindow = launchedAppComponentMatcherOverride ?: componentMatcher
-            val builder =
-                wmHelper
-                    .StateSyncBuilder()
-                    .add(WindowManagerConditionsFactory.isWMStateComplete())
-                    .withAppTransitionIdle()
-                    .withWindowSurfaceAppeared(expectedWindow)
-
-            waitConditions.forEach { builder.add(it) }
-            builder.waitForAndVerify()
-
-            // During seamless rotation the app window is shown
-            val currWmState = wmHelper.currentState.wmState
-            if (currWmState.visibleWindows.none { it.isFullscreen }) {
-                wmHelper
-                    .StateSyncBuilder()
-                    .withNavOrTaskBarVisible()
-                    .withStatusBarVisible()
-                    .waitForAndVerify()
-            }
-        }
-    }
-
-    fun isAvailable(): Boolean {
-        return try {
-            pkgManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
-            true
-        } catch (e: PackageManager.NameNotFoundException) {
-            false
-        }
-    }
-
-    companion object {
-        private const val APP_LAUNCH_WAIT_TIME_MS = 10000L
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt
deleted file mode 100644
index 0bbaabc..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/WindowUtils.kt
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.helpers
-
-import android.graphics.Rect
-import androidx.collection.LruCache
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.common.layers.Display
-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.windowmanager.windows.DisplayContent
-import com.android.server.wm.traces.parser.getCurrentStateDump
-import com.android.server.wm.traces.parser.toAndroidRect
-
-object WindowUtils {
-
-    private val displayBoundsCache = LruCache<PlatformConsts.Rotation, Region>(1)
-    private val instrumentation = InstrumentationRegistry.getInstrumentation()
-
-    /** Helper functions to retrieve system window sizes and positions. */
-    private val context = instrumentation.context
-
-    private val resources
-        get() = context.getResources()
-
-    /** Get the display bounds */
-    val displayBounds: Rect
-        get() {
-            val currState =
-                getCurrentStateDump(instrumentation.uiAutomation, clearCacheAfterParsing = false)
-            return currState.layerState.physicalDisplay?.layerStackSpace?.toAndroidRect() ?: Rect()
-        }
-
-    /** Gets the current display rotation */
-    val displayRotation: PlatformConsts.Rotation
-        get() {
-            val currState =
-                getCurrentStateDump(instrumentation.uiAutomation, clearCacheAfterParsing = false)
-
-            return currState.wmState.getRotation(PlatformConsts.DEFAULT_DISPLAY)
-        }
-
-    /**
-     * Get the display bounds when the device is at a specific rotation
-     *
-     * @param requestedRotation Device rotation
-     */
-    fun getDisplayBounds(requestedRotation: PlatformConsts.Rotation): Region {
-        return displayBoundsCache[requestedRotation]
-            ?: let {
-                val displayIsRotated = displayRotation.isRotated()
-                val requestedDisplayIsRotated = requestedRotation.isRotated()
-
-                // if the current orientation changes with the requested rotation,
-                // flip height and width of display bounds.
-                val displayBounds = displayBounds
-                val retval: Region
-                if (displayIsRotated != requestedDisplayIsRotated) {
-                    retval = Region.from(0, 0, displayBounds.height(), displayBounds.width())
-                } else {
-                    retval = Region.from(0, 0, displayBounds.width(), displayBounds.height())
-                }
-                displayBoundsCache.put(requestedRotation, retval)
-                return retval
-            }
-    }
-    /** Gets the status bar height with a specific display cutout. */
-    private fun getExpectedStatusBarHeight(displayContent: DisplayContent): Int {
-        val cutout = displayContent.cutout
-        val defaultSize = status_bar_height_default
-        val safeInsetTop = cutout?.insets?.top ?: 0
-        val waterfallInsetTop = cutout?.waterfallInsets?.top ?: 0
-        // The status bar height should be:
-        // Max(top cutout size, (status bar default height + waterfall top size))
-        return safeInsetTop.coerceAtLeast(defaultSize + waterfallInsetTop)
-    }
-
-    /**
-     * Gets the expected status bar position for a specific display
-     *
-     * @param display the main display
-     */
-    fun getExpectedStatusBarPosition(display: DisplayContent): Region {
-        val height = getExpectedStatusBarHeight(display)
-        return Region.from(0, 0, display.displayRect.width, height)
-    }
-
-    /**
-     * Gets the expected navigation bar position for a specific display
-     *
-     * @param display the main display
-     */
-    fun getNavigationBarPosition(display: Display): Region {
-        return getNavigationBarPosition(display, isGesturalNavigationEnabled)
-    }
-
-    /**
-     * Gets the expected navigation bar position for a specific display
-     *
-     * @param display the main display
-     * @param isGesturalNavigation whether gestural navigation is enabled
-     */
-    fun getNavigationBarPosition(display: Display, isGesturalNavigation: Boolean): Region {
-        val navBarWidth = getDimensionPixelSize("navigation_bar_width")
-        val displayHeight = display.layerStackSpace.height
-        val displayWidth = display.layerStackSpace.width
-        val requestedRotation = display.transform.getRotation()
-        val navBarHeight = getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation)
-
-        return when {
-            // nav bar is at the bottom of the screen
-            !requestedRotation.isRotated() || isGesturalNavigation ->
-                Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
-            // nav bar is on the right side
-            requestedRotation == PlatformConsts.Rotation.ROTATION_90 ->
-                Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
-            // nav bar is on the left side
-            requestedRotation == PlatformConsts.Rotation.ROTATION_270 ->
-                Region.from(0, 0, navBarWidth, displayHeight)
-            else -> error("Unknown rotation $requestedRotation")
-        }
-    }
-
-    /**
-     * Estimate the navigation bar position at a specific rotation
-     *
-     * @param requestedRotation Device rotation
-     */
-    fun estimateNavigationBarPosition(requestedRotation: PlatformConsts.Rotation): Region {
-        val displayBounds = displayBounds
-        val displayWidth: Int
-        val displayHeight: Int
-        if (!requestedRotation.isRotated()) {
-            displayWidth = displayBounds.width()
-            displayHeight = displayBounds.height()
-        } else {
-            // swap display dimensions in landscape or seascape mode
-            displayWidth = displayBounds.height()
-            displayHeight = displayBounds.width()
-        }
-        val navBarWidth = getDimensionPixelSize("navigation_bar_width")
-        val navBarHeight =
-            getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation = false)
-
-        return when {
-            // nav bar is at the bottom of the screen
-            !requestedRotation.isRotated() || isGesturalNavigationEnabled ->
-                Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
-            // nav bar is on the right side
-            requestedRotation == PlatformConsts.Rotation.ROTATION_90 ->
-                Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
-            // nav bar is on the left side
-            requestedRotation == PlatformConsts.Rotation.ROTATION_270 ->
-                Region.from(0, 0, navBarWidth, displayHeight)
-            else -> error("Unknown rotation $requestedRotation")
-        }
-    }
-
-    /** Checks if the device uses gestural navigation */
-    val isGesturalNavigationEnabled: Boolean
-        get() {
-            val resourceId =
-                resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
-            return resources.getInteger(resourceId) == 2 /* NAV_BAR_MODE_GESTURAL */
-        }
-
-    fun getDimensionPixelSize(resourceName: String): Int {
-        val resourceId = resources.getIdentifier(resourceName, "dimen", "android")
-        return resources.getDimensionPixelSize(resourceId)
-    }
-
-    /** Gets the navigation bar frame height */
-    fun getNavigationBarFrameHeight(
-        rotation: PlatformConsts.Rotation,
-        isGesturalNavigation: Boolean
-    ): Int {
-        return if (rotation.isRotated()) {
-            if (isGesturalNavigation) {
-                getDimensionPixelSize("navigation_bar_frame_height")
-            } else {
-                getDimensionPixelSize("navigation_bar_height_landscape")
-            }
-        } else {
-            getDimensionPixelSize("navigation_bar_frame_height")
-        }
-    }
-
-    private val status_bar_height_default: Int
-        get() {
-            val resourceId =
-                resources.getIdentifier("status_bar_height_default", "dimen", "android")
-            return resources.getDimensionPixelSize(resourceId)
-        }
-
-    val quick_qs_offset_height: Int
-        get() {
-            val resourceId = resources.getIdentifier("quick_qs_offset_height", "dimen", "android")
-            return resources.getDimensionPixelSize(resourceId)
-        }
-
-    /** Split screen divider inset height */
-    val dockedStackDividerInset: Int
-        get() {
-            val resourceId =
-                resources.getIdentifier("docked_stack_divider_insets", "dimen", "android")
-            return resources.getDimensionPixelSize(resourceId)
-        }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/helpers/YouTubeAppHelper.kt b/libraries/flicker/src/com/android/server/wm/flicker/helpers/YouTubeAppHelper.kt
deleted file mode 100644
index 8cfc4f3..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/helpers/YouTubeAppHelper.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-/**
- * Helper to launch Youtube (not compatible with AOSP)
- *
- * This helper has no other functionality but the app launch.
- */
-class YouTubeAppHelper(
-    instrumentation: Instrumentation,
-    pkgManager: PackageManager = instrumentation.context.packageManager
-) :
-    StandardAppHelper(
-        instrumentation,
-        getYoutubeLauncherName(pkgManager),
-        getYoutubeComponent(pkgManager)
-    ) {
-    companion object {
-        private fun getYoutubeIntent(pkgManager: PackageManager): Intent {
-            return pkgManager.getLaunchIntentForPackage("com.google.android.youtube")
-                ?: error("Youtube launch intent not found")
-        }
-
-        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
-            val intent = getYoutubeIntent(pkgManager)
-            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                ?: error("unable to resolve calendar activity")
-        }
-
-        private fun getYoutubeComponent(pkgManager: PackageManager): ComponentNameMatcher {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return ComponentNameMatcher(
-                resolveInfo.activityInfo.packageName,
-                className = resolveInfo.activityInfo.name
-            )
-        }
-
-        private fun getYoutubeLauncherName(pkgManager: PackageManager): String {
-            val resolveInfo = getResolveInfo(pkgManager)
-            return resolveInfo.loadLabel(pkgManager).toString()
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/io/IResultData.kt b/libraries/flicker/src/com/android/server/wm/flicker/io/IResultData.kt
deleted file mode 100644
index 15214bc..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/io/IResultData.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.io
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TransitionTimeRange
-import java.io.File
-
-/** Contents of a flicker run (e.g. files, status, event log) */
-interface IResultData {
-    val transitionTimeRange: TransitionTimeRange
-    val executionError: Throwable?
-    val artifact: File
-    val runStatus: RunStatus
-
-    fun getArtifactBytes(): ByteArray
-
-    /** updates the artifact status to [newStatus] */
-    fun getNewFilePath(newStatus: RunStatus): File {
-        val currTestName = artifact.name.dropWhile { it != '_' }
-        return artifact.resolveSibling("${newStatus.prefix}_$currTestName")
-    }
-
-    /** updates the artifact status to [newStatus] */
-    fun updateStatus(newStatus: RunStatus): IResultData
-
-    /** @return a subsection of the trace */
-    fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): IResultData
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultData.kt b/libraries/flicker/src/com/android/server/wm/flicker/io/ResultData.kt
deleted file mode 100644
index 8fc0eca..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultData.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TransitionTimeRange
-import java.io.File
-
-/**
- * Contents of a flicker run (e.g. files, status, event log)
- *
- * @param _artifact Path to the artifact file
- * @param _transitionTimeRange Transition start and end time
- * @param _executionError Transition execution error (if any)
- * @param _runStatus Status of the run
- */
-open class ResultData(
-    _artifact: File,
-    _transitionTimeRange: TransitionTimeRange,
-    _executionError: Throwable?,
-    _runStatus: RunStatus
-) : IResultData {
-    final override var artifact: File = _artifact
-        private set
-    final override var transitionTimeRange: TransitionTimeRange = _transitionTimeRange
-        private set
-    final override var executionError: Throwable? = _executionError
-        private set
-    final override var runStatus: RunStatus = _runStatus
-        private set
-
-    /** {@inheritDoc} */
-    override fun getArtifactBytes(): ByteArray = artifact.readBytes()
-
-    /** {@inheritDoc} */
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp) = apply {
-        require(startTimestamp.hasAllTimestamps)
-        require(endTimestamp.hasAllTimestamps)
-        return ResultData(
-            artifact,
-            TransitionTimeRange(startTimestamp, endTimestamp),
-            executionError,
-            runStatus
-        )
-    }
-
-    override fun toString(): String = buildString {
-        append(artifact)
-        append(" (status=")
-        append(runStatus)
-        executionError?.let {
-            append(", error=")
-            append(it.message)
-        }
-        append(") ")
-    }
-
-    /** {@inheritDoc} */
-    override fun updateStatus(newStatus: RunStatus) = apply {
-        val currFile = artifact
-        require(RunStatus.fromFileName(currFile.name) != RunStatus.UNDEFINED) {
-            "File name should start with a value from `RunStatus`, instead it was $currFile"
-        }
-        val newFile = getNewFilePath(newStatus)
-        if (currFile != newFile) {
-            Utils.moveFile(currFile, newFile)
-            artifact = newFile
-            runStatus = newStatus
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReader.kt b/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReader.kt
deleted file mode 100644
index dadc0f9..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReader.kt
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.flicker.io
-
-import androidx.annotation.VisibleForTesting
-import com.android.server.wm.flicker.TraceConfig
-import com.android.server.wm.flicker.TraceConfigs
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.CujTrace
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.io.BUFFER_SIZE
-import com.android.server.wm.traces.common.io.FLICKER_IO_TAG
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.io.ResultArtifactDescriptor
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.parser.events.EventLogParser
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.parser.layers.LayersTraceParser
-import com.android.server.wm.traces.parser.transaction.TransactionsTraceParser
-import com.android.server.wm.traces.parser.transition.TransitionsTraceParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerDumpParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.IOException
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-
-/**
- * Helper class to read results from a flicker artifact
- *
- * @param result to read from
- * @param traceConfig
- */
-open class ResultReader(internal var result: IResultData, internal val traceConfig: TraceConfigs) :
-    IReader {
-    override val artifactPath: String
-        get() = result.artifact.absolutePath
-    override val runStatus
-        get() = result.runStatus
-    internal val transitionTimeRange
-        get() = result.transitionTimeRange
-    override val isFailure
-        get() = runStatus.isFailure
-    override val executionError
-        get() = result.executionError
-
-    private fun withZipFile(predicate: (ZipInputStream) -> Unit) {
-        val zipInputStream =
-            ZipInputStream(
-                BufferedInputStream(ByteArrayInputStream(result.getArtifactBytes()), BUFFER_SIZE)
-            )
-        try {
-            predicate(zipInputStream)
-        } finally {
-            zipInputStream.closeEntry()
-            zipInputStream.close()
-        }
-    }
-
-    private fun forEachFileInZip(predicate: (ZipEntry) -> Unit) {
-        withZipFile {
-            var zipEntry: ZipEntry? = it.nextEntry
-            while (zipEntry != null) {
-                predicate(zipEntry)
-                zipEntry = it.nextEntry
-            }
-        }
-    }
-
-    @Throws(IOException::class)
-    private fun readFromZip(descriptor: ResultArtifactDescriptor): ByteArray? {
-        CrossPlatform.log.d(FLICKER_IO_TAG, "Reading descriptor=$descriptor from $result")
-
-        var foundFile = false
-        val outByteArray = ByteArrayOutputStream()
-        val tmpBuffer = ByteArray(BUFFER_SIZE)
-        withZipFile {
-            var zipEntry: ZipEntry? = it.nextEntry
-            while (zipEntry != null) {
-                if (zipEntry.name == descriptor.fileNameInArtifact) {
-                    val outputStream = BufferedOutputStream(outByteArray, BUFFER_SIZE)
-                    try {
-                        var size = it.read(tmpBuffer, 0, BUFFER_SIZE)
-                        while (size > 0) {
-                            outputStream.write(tmpBuffer, 0, size)
-                            size = it.read(tmpBuffer, 0, BUFFER_SIZE)
-                        }
-                        it.closeEntry()
-                    } finally {
-                        outputStream.flush()
-                        outputStream.close()
-                    }
-                    foundFile = true
-                    break
-                }
-                zipEntry = it.nextEntry
-            }
-        }
-
-        return if (foundFile) outByteArray.toByteArray() else null
-    }
-
-    override fun readBytes(traceType: TraceType, tag: String): ByteArray? =
-        readFromZip(ResultArtifactDescriptor(traceType, tag))
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readWmState(tag: String): WindowManagerTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.WM_DUMP, tag)
-        CrossPlatform.log.d(FLICKER_IO_TAG, "Reading WM trace descriptor=$descriptor from $result")
-        val traceData = readFromZip(descriptor)
-        return traceData?.let { WindowManagerDumpParser().parse(it, clearCache = true) }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readWmTrace(): WindowManagerTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.WM)
-        return readFromZip(descriptor)?.let {
-            val trace =
-                WindowManagerTraceParser()
-                    .parse(
-                        it,
-                        from = transitionTimeRange.start,
-                        to = transitionTimeRange.end,
-                        addInitialEntry = true,
-                        clearCache = true
-                    )
-            val minimumEntries = minimumTraceEntriesForConfig(traceConfig.wmTrace)
-            require(trace.entries.size >= minimumEntries) {
-                "WM trace contained ${trace.entries.size} entries, " +
-                    "expected at least $minimumEntries... :: " +
-                    "transition starts at ${transitionTimeRange.start} and " +
-                    "ends at ${transitionTimeRange.end}."
-            }
-            trace
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readLayersTrace(): LayersTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.SF)
-        return readFromZip(descriptor)?.let {
-            val trace =
-                LayersTraceParser()
-                    .parse(
-                        it,
-                        transitionTimeRange.start,
-                        transitionTimeRange.end,
-                        addInitialEntry = true,
-                        clearCache = true
-                    )
-            val minimumEntries = minimumTraceEntriesForConfig(traceConfig.layersTrace)
-            require(trace.entries.size >= minimumEntries) {
-                "Layers trace contained ${trace.entries.size} entries, " +
-                    "expected at least $minimumEntries... :: " +
-                    "transition starts at ${transitionTimeRange.start} and " +
-                    "ends at ${transitionTimeRange.end}."
-            }
-            trace
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readLayersDump(tag: String): LayersTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.SF_DUMP, tag)
-        val traceData = readFromZip(descriptor)
-        return traceData?.let { LayersTraceParser().parse(it, clearCache = true) }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readTransactionsTrace(): TransactionsTrace? =
-        doReadTransactionsTrace(from = transitionTimeRange.start, to = transitionTimeRange.end)
-
-    private fun doReadTransactionsTrace(from: Timestamp, to: Timestamp): TransactionsTrace? {
-        val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSACTION))
-        return traceData?.let {
-            val trace = TransactionsTraceParser().parse(it, from, to, addInitialEntry = true)
-            require(trace.entries.isNotEmpty()) { "Transactions trace cannot be empty" }
-            trace
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readTransitionsTrace(): TransitionsTrace? {
-        val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSITION)) ?: return null
-
-        val fullTrace = TransitionsTraceParser().parse(traceData)
-        val trace = fullTrace.slice(transitionTimeRange.start, transitionTimeRange.end)
-        if (!traceConfig.transitionsTrace.allowNoChange) {
-            require(trace.entries.isNotEmpty()) { "Transitions trace cannot be empty" }
-        }
-        return trace
-    }
-
-    private fun minimumTraceEntriesForConfig(config: TraceConfig): Int {
-        return if (config.allowNoChange) 1 else 2
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readEventLogTrace(): EventLog? {
-        val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
-        return readFromZip(descriptor)?.let {
-            EventLogParser()
-                .parse(it, from = transitionTimeRange.start, to = transitionTimeRange.end)
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * @throws IOException if the artifact file doesn't exist or can't be read
-     */
-    @Throws(IOException::class)
-    override fun readCujTrace(): CujTrace? = readEventLogTrace()?.cujTrace
-
-    /** @return an [IReader] for the subsection of the trace we are reading in this reader */
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReader {
-        val slicedResult = result.slice(startTimestamp, endTimestamp)
-        return ResultReader(slicedResult, traceConfig)
-    }
-
-    override fun toString(): String = "$result"
-
-    /** @return the number of files in the artifact */
-    @VisibleForTesting
-    fun countFiles(): Int {
-        var count = 0
-        forEachFileInZip { count++ }
-        return count
-    }
-
-    /** @return if a file with type [traceType] linked to a [tag] exists in the artifact */
-    fun hasTraceFile(traceType: TraceType, tag: String = AssertionTag.ALL): Boolean {
-        val descriptor = ResultArtifactDescriptor(traceType, tag)
-        var found = false
-        forEachFileInZip { found = found || (it.name == descriptor.fileNameInArtifact) }
-        return found
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReaderWithLru.kt b/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReaderWithLru.kt
deleted file mode 100644
index 73917c3..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultReaderWithLru.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.io
-
-import androidx.collection.LruCache
-import com.android.server.wm.flicker.TraceConfigs
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.io.ResultArtifactDescriptor
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.io.TransitionTimeRange
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import java.io.IOException
-
-/**
- * Helper class to read results from a flicker artifact using a LRU
- *
- * @param result to read from
- * @param traceConfig
- */
-open class ResultReaderWithLru(
-    result: IResultData,
-    traceConfig: TraceConfigs,
-    private val reader: ResultReader = ResultReader(result, traceConfig)
-) : IReader by reader {
-    /** {@inheritDoc} */
-    @Throws(IOException::class)
-    override fun readWmTrace(): WindowManagerTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.SF)
-        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
-        val trace = wmTraceCache[key] ?: reader.readWmTrace()
-        return trace?.also { wmTraceCache.put(key, trace) }
-    }
-
-    /** {@inheritDoc} */
-    @Throws(IOException::class)
-    override fun readLayersTrace(): LayersTrace? {
-        val descriptor = ResultArtifactDescriptor(TraceType.SF)
-        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
-        val trace = layersTraceCache[key] ?: reader.readLayersTrace()
-        return trace?.also { layersTraceCache.put(key, trace) }
-    }
-
-    /** {@inheritDoc} */
-    @Throws(IOException::class)
-    override fun readEventLogTrace(): EventLog? {
-        val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
-        val key = CacheKey(reader.artifactPath, descriptor, reader.transitionTimeRange)
-        val trace = eventLogCache[key] ?: reader.readEventLogTrace()
-        return trace?.also { eventLogCache.put(key, trace) }
-    }
-
-    /** {@inheritDoc} */
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReaderWithLru {
-        val slicedReader = reader.slice(startTimestamp, endTimestamp)
-        return ResultReaderWithLru(slicedReader.result, slicedReader.traceConfig, slicedReader)
-    }
-
-    companion object {
-        data class CacheKey(
-            private val artifact: String,
-            private val descriptor: ResultArtifactDescriptor,
-            private val transitionTimeRange: TransitionTimeRange
-        )
-
-        private val wmTraceCache = LruCache<CacheKey, WindowManagerTrace>(1)
-        private val layersTraceCache = LruCache<CacheKey, LayersTrace>(1)
-        private val eventLogCache = LruCache<CacheKey, EventLog>(1)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultWriter.kt b/libraries/flicker/src/com/android/server/wm/flicker/io/ResultWriter.kt
deleted file mode 100644
index 2aaa0b0..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/io/ResultWriter.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.now
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.BUFFER_SIZE
-import com.android.server.wm.traces.common.io.FLICKER_IO_TAG
-import com.android.server.wm.traces.common.io.ResultArtifactDescriptor
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.io.TransitionTimeRange
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipOutputStream
-
-/** Helper class to create run result artifact files */
-open class ResultWriter {
-    protected var scenario: IScenario = ScenarioBuilder().createEmptyScenario()
-    private var runStatus: RunStatus = RunStatus.UNDEFINED
-    private val files = mutableMapOf<ResultArtifactDescriptor, File>()
-    private var transitionStartTime = CrossPlatform.timestamp.min()
-    private var transitionEndTime = CrossPlatform.timestamp.max()
-    private var executionError: Throwable? = null
-    private var outputDir: File? = null
-
-    /** Sets the artifact scenario to [_scenario] */
-    fun forScenario(_scenario: IScenario) = apply { scenario = _scenario }
-
-    /** Sets the artifact transition start time to [time] */
-    fun setTransitionStartTime(time: Timestamp = now()) = apply { transitionStartTime = time }
-
-    /** Sets the artifact transition end time to [time] */
-    fun setTransitionEndTime(time: Timestamp = now()) = apply { transitionEndTime = time }
-
-    /** Sets the artifact status as successfully executed transition ([RunStatus.RUN_EXECUTED]) */
-    fun setRunComplete() = apply { runStatus = RunStatus.RUN_EXECUTED }
-
-    /** Sets the dir where the artifact file will be stored to [dir] */
-    fun withOutputDir(dir: File) = apply { outputDir = dir }
-
-    /**
-     * Sets the artifact status as failed executed transition ([RunStatus.RUN_FAILED])
-     *
-     * @param error that caused the transition to fail
-     */
-    fun setRunFailed(error: Throwable) = apply {
-        runStatus = RunStatus.RUN_FAILED
-        executionError = error
-    }
-
-    /**
-     * Adds [artifact] to the result artifact
-     *
-     * @param traceType used when adding [artifact] to the result artifact
-     * @param tag used when adding [artifact] to the result artifact
-     */
-    fun addTraceResult(traceType: TraceType, artifact: File, tag: String = AssertionTag.ALL) =
-        apply {
-            CrossPlatform.log.d(
-                FLICKER_IO_TAG,
-                "Add trace result file=$artifact type=$traceType tag=$tag scenario=$scenario"
-            )
-            val fileDescriptor = ResultArtifactDescriptor(traceType, tag)
-            files[fileDescriptor] = artifact
-        }
-
-    private fun addFile(zipOutputStream: ZipOutputStream, artifact: File, nameInArchive: String) {
-        CrossPlatform.log.v(FLICKER_IO_TAG, "Adding $artifact with name $nameInArchive to zip")
-        val fi = FileInputStream(artifact)
-        val inputStream = BufferedInputStream(fi, BUFFER_SIZE)
-        inputStream.use {
-            val entry = ZipEntry(nameInArchive)
-            zipOutputStream.putNextEntry(entry)
-            val data = ByteArray(BUFFER_SIZE)
-            var count: Int = it.read(data, 0, BUFFER_SIZE)
-            while (count != -1) {
-                zipOutputStream.write(data, 0, count)
-                count = it.read(data, 0, BUFFER_SIZE)
-            }
-        }
-        zipOutputStream.closeEntry()
-        artifact.deleteIfExists()
-    }
-
-    private fun createZipFile(file: File): ZipOutputStream {
-        return ZipOutputStream(BufferedOutputStream(FileOutputStream(file), BUFFER_SIZE))
-    }
-
-    /** @return writes the result artifact to disk and returns it */
-    open fun write(): IResultData {
-        return CrossPlatform.log.withTracing("write") {
-            val outputDir = outputDir
-            requireNotNull(outputDir) { "Output dir not configured" }
-            require(!scenario.isEmpty) { "Scenario shouldn't be empty" }
-            // Ensure output directory exists
-            outputDir.mkdirs()
-
-            if (runStatus == RunStatus.UNDEFINED) {
-                CrossPlatform.log.w(FLICKER_IO_TAG, "Writing result with $runStatus run status")
-            }
-
-            val newFileName = "${runStatus.prefix}_$scenario.zip"
-            val dstFile = outputDir.resolve(newFileName)
-            CrossPlatform.log.d(FLICKER_IO_TAG, "Writing artifact file $dstFile")
-            createZipFile(dstFile).use { zipOutputStream ->
-                files.forEach { (descriptor, artifact) ->
-                    addFile(
-                        zipOutputStream,
-                        artifact,
-                        nameInArchive = descriptor.fileNameInArtifact
-                    )
-                }
-            }
-
-            ResultData(
-                dstFile,
-                TransitionTimeRange(transitionStartTime, transitionEndTime),
-                executionError,
-                runStatus
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/AbstractFlickerRunnerDecorator.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/AbstractFlickerRunnerDecorator.kt
deleted file mode 100644
index 14515a9..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/AbstractFlickerRunnerDecorator.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.runner.TransitionRunner
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.Scenario
-import java.lang.reflect.Modifier
-import org.junit.runner.Description
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.TestClass
-
-abstract class AbstractFlickerRunnerDecorator(
-    protected val testClass: TestClass,
-    protected val scenario: Scenario?,
-    protected val inner: IFlickerJUnitDecorator?
-) : IFlickerJUnitDecorator {
-    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-
-    final override fun doValidateConstructor(): List<Throwable> {
-        val errors = internalDoValidateConstructor().toMutableList()
-        if (errors.isEmpty()) {
-            inner?.doValidateConstructor()?.let { errors.addAll(it) }
-        }
-        return errors
-    }
-
-    final override fun doValidateInstanceMethods(): List<Throwable> {
-        val errors = internalDoValidateInstanceMethods().toMutableList()
-        if (errors.isEmpty()) {
-            inner?.doValidateInstanceMethods()?.let { errors.addAll(it) }
-        }
-        return errors
-    }
-
-    private fun internalDoValidateConstructor(): List<Throwable> {
-        val errors = mutableListOf<Throwable>()
-        val ctor = testClass.javaClass.constructors.firstOrNull()
-        if (ctor?.parameterTypes?.none { it == FlickerTest::class.java } != false) {
-            errors.add(
-                IllegalStateException(
-                    "Constructor should have a parameter of type " +
-                        FlickerTest::class.java.simpleName
-                )
-            )
-        }
-        return errors
-    }
-
-    /** Validate that the test has one method annotated with [FlickerBuilderProvider] */
-    private fun internalDoValidateInstanceMethods(): List<Throwable> {
-        val errors = mutableListOf<Throwable>()
-        val methods = getCandidateProviderMethods(testClass)
-
-        if (methods.isEmpty() || methods.size > 1) {
-            val prefix = if (methods.isEmpty()) "One" else "Only one"
-            errors.add(
-                IllegalArgumentException(
-                    "$prefix object should be annotated with @FlickerBuilderProvider"
-                )
-            )
-        } else {
-            val method = methods.first()
-
-            if (Modifier.isStatic(method.method.modifiers)) {
-                errors.add(IllegalArgumentException("Method ${method.name}() should not be static"))
-            }
-            if (!Modifier.isPublic(method.method.modifiers)) {
-                errors.add(IllegalArgumentException("Method ${method.name}() should be public"))
-            }
-            if (method.returnType != FlickerBuilder::class.java) {
-                errors.add(
-                    IllegalArgumentException(
-                        "Method ${method.name}() should return a " +
-                            "${FlickerBuilder::class.java.simpleName} object"
-                    )
-                )
-            }
-            if (method.method.parameterTypes.isNotEmpty()) {
-                errors.add(
-                    IllegalArgumentException("Method ${method.name} should have no parameters")
-                )
-            }
-        }
-
-        return errors
-    }
-
-    protected fun doRunTransition(test: Any, description: Description?) {
-        CrossPlatform.log.d(FLICKER_TAG, "$scenario - Setting up FaaS")
-        val scenario = scenario
-        requireNotNull(scenario) { "Expected to have a scenario to run" }
-        if (!DataStore.containsResult(scenario)) {
-            CrossPlatform.log.v(FLICKER_TAG, "Creating flicker object for $scenario")
-            val builder = getFlickerBuilder(test)
-            CrossPlatform.log.v(FLICKER_TAG, "Creating flicker object for $scenario")
-            val flicker = builder.build()
-            val runner = TransitionRunner(scenario, instrumentation)
-            CrossPlatform.log.v(FLICKER_TAG, "Running transition for $scenario")
-            runner.execute(flicker, description)
-        }
-    }
-
-    private val providerMethod: FrameworkMethod
-        get() =
-            getCandidateProviderMethods(testClass).firstOrNull()
-                ?: error("Provider method not found")
-
-    private fun getFlickerBuilder(test: Any): FlickerBuilder {
-        CrossPlatform.log.v(FLICKER_TAG, "Obtaining flicker builder for $testClass")
-        return providerMethod.invokeExplosively(test) as FlickerBuilder
-    }
-
-    companion object {
-        private fun getCandidateProviderMethods(testClass: TestClass): List<FrameworkMethod> =
-            testClass.getAnnotatedMethods(FlickerBuilderProvider::class.java) ?: emptyList()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunner.kt
deleted file mode 100644
index f6773a0..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunner.kt
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.os.Bundle
-import android.platform.test.util.TestFilter
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.common.Scenario
-import java.util.Collections
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.internal.AssumptionViolatedException
-import org.junit.internal.runners.model.EachTestNotifier
-import org.junit.runner.Description
-import org.junit.runner.manipulation.Filter
-import org.junit.runner.manipulation.InvalidOrderingException
-import org.junit.runner.manipulation.NoTestsRemainException
-import org.junit.runner.manipulation.Orderable
-import org.junit.runner.manipulation.Orderer
-import org.junit.runner.manipulation.Sorter
-import org.junit.runner.notification.RunNotifier
-import org.junit.runner.notification.StoppedByUserException
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.RunnerScheduler
-import org.junit.runners.model.Statement
-import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters
-import org.junit.runners.parameterized.TestWithParameters
-
-/**
- * Implements the JUnit 4 standard test case class model, parsing from a flicker DSL.
- *
- * Supports both assertions in {@link org.junit.Test} and assertions defined in the DSL
- *
- * When using this runner the default `atest class#method` command doesn't work. Instead use: --
- * --test-arg \
- * ```
- *     com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests:=<TEST_NAME>
- * ```
- * For example: `atest FlickerTests -- \
- * ```
- *     --test-arg com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests\
- *     :=com.android.server.wm.flicker.close.\
- *     CloseAppBackButtonTest#launcherWindowBecomesVisible[ROTATION_90_GESTURAL_NAV]`
- * ```
- */
-class FlickerBlockJUnit4ClassRunner(test: TestWithParameters?, private val scenario: Scenario?) :
-    BlockJUnit4ClassRunnerWithParameters(test), IFlickerJUnitDecorator {
-
-    private val arguments: Bundle = InstrumentationRegistry.getArguments()
-    private val flickerDecorator =
-        test?.let {
-            FlickerServiceDecorator(
-                test.testClass,
-                scenario,
-                inner = LegacyFlickerDecorator(test.testClass, scenario, inner = this)
-            )
-        }
-
-    override fun run(notifier: RunNotifier) {
-        val testNotifier = EachTestNotifier(notifier, description)
-        testNotifier.fireTestSuiteStarted()
-        try {
-            val statement = classBlock(notifier)
-            statement.evaluate()
-        } catch (e: AssumptionViolatedException) {
-            testNotifier.addFailedAssumption(e)
-        } catch (e: StoppedByUserException) {
-            throw e
-        } catch (e: Throwable) {
-            testNotifier.addFailure(e)
-        } finally {
-            testNotifier.fireTestSuiteFinished()
-        }
-    }
-
-    /**
-     * Implementation of Filterable and Sortable Based on JUnit's ParentRunner implementation but
-     * with a minor modification to ensure injected FaaS tests are not filtered out.
-     */
-    @Throws(NoTestsRemainException::class)
-    override fun filter(filter: Filter) {
-        childrenLock.lock()
-        try {
-            val children: MutableList<FrameworkMethod> = getFilteredChildren().toMutableList()
-            val iter: MutableIterator<FrameworkMethod> = children.iterator()
-            while (iter.hasNext()) {
-                val each: FrameworkMethod = iter.next()
-                if (isInjectedFaasTest(each)) {
-                    // Don't filter out injected FaaS tests
-                    continue
-                }
-                if (shouldRun(filter, each)) {
-                    try {
-                        filter.apply(each)
-                    } catch (e: NoTestsRemainException) {
-                        iter.remove()
-                    }
-                } else {
-                    iter.remove()
-                }
-            }
-            filteredChildren = Collections.unmodifiableList(children)
-            if (filteredChildren!!.isEmpty()) {
-                throw NoTestsRemainException()
-            }
-        } finally {
-            childrenLock.unlock()
-        }
-    }
-
-    private fun isInjectedFaasTest(method: FrameworkMethod): Boolean {
-        return method is FlickerServiceCachedTestCase
-    }
-
-    override fun isIgnored(child: FrameworkMethod): Boolean {
-        return child.getAnnotation(Ignore::class.java) != null
-    }
-
-    /**
-     * Returns the methods that run tests. Is ran after validateInstanceMethods, so
-     * flickerBuilderProviderMethod should be set.
-     */
-    public override fun computeTestMethods(): List<FrameworkMethod> {
-        val result = mutableListOf<FrameworkMethod>()
-        if (scenario != null) {
-            val testInstance = createTest()
-            result.addAll(flickerDecorator?.getTestMethods(testInstance) ?: emptyList())
-        }
-        return result
-    }
-
-    override fun describeChild(method: FrameworkMethod?): Description {
-        return flickerDecorator?.getChildDescription(method)
-            ?: error("There are no children to describe")
-    }
-
-    /** {@inheritDoc} */
-    override fun getChildren(): MutableList<FrameworkMethod> {
-        val validChildren =
-            super.getChildren().filter {
-                val childDescription = describeChild(it)
-                TestFilter.isFilteredOrUnspecified(arguments, childDescription)
-            }
-        return validChildren.toMutableList()
-    }
-
-    override fun methodInvoker(method: FrameworkMethod, test: Any): Statement {
-        return flickerDecorator?.getMethodInvoker(method, test)
-            ?: error("No statements to invoke for $method in $test")
-    }
-
-    override fun validateConstructor(errors: MutableList<Throwable>) {
-        super.validateConstructor(errors)
-
-        if (errors.isEmpty()) {
-            flickerDecorator?.doValidateConstructor()?.let { errors.addAll(it) }
-        }
-    }
-
-    @Deprecated("Deprecated in Java")
-    override fun validateInstanceMethods(errors: MutableList<Throwable>?) {
-        flickerDecorator?.doValidateInstanceMethods()?.let { errors?.addAll(it) }
-    }
-
-    /** IFlickerJunitDecorator implementation */
-    override fun getTestMethods(test: Any): List<FrameworkMethod> {
-        val tests = mutableListOf<FrameworkMethod>()
-        tests.addAll(super.computeTestMethods())
-        return tests
-    }
-
-    override fun getChildDescription(method: FrameworkMethod?): Description? {
-        return super.describeChild(method)
-    }
-
-    override fun doValidateInstanceMethods(): List<Throwable> {
-        val errors = mutableListOf<Throwable>()
-        super.validateInstanceMethods(errors)
-        return errors
-    }
-
-    override fun doValidateConstructor(): List<Throwable> {
-        val result = mutableListOf<Throwable>()
-        super.validateConstructor(result)
-        return result
-    }
-
-    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
-        return super.methodInvoker(method, test)
-    }
-
-    /**
-     * ********************************************************************************************
-     * START of code copied from ParentRunner to have local access to filteredChildren to ensure
-     * FaaS injected tests are not filtered out.
-     */
-
-    // Guarded by childrenLock
-    @Volatile private var filteredChildren: List<FrameworkMethod>? = null
-    private val childrenLock: Lock = ReentrantLock()
-
-    @Volatile
-    private var scheduler: RunnerScheduler =
-        object : RunnerScheduler {
-            override fun schedule(childStatement: Runnable) {
-                childStatement.run()
-            }
-
-            override fun finished() {
-                // do nothing
-            }
-        }
-
-    /**
-     * Sets a scheduler that determines the order and parallelization of children. Highly
-     * experimental feature that may change.
-     */
-    override fun setScheduler(scheduler: RunnerScheduler) {
-        this.scheduler = scheduler
-    }
-
-    private fun shouldRun(filter: Filter, each: FrameworkMethod): Boolean {
-        return filter.shouldRun(describeChild(each))
-    }
-
-    override fun sort(sorter: Sorter) {
-        if (shouldNotReorder()) {
-            return
-        }
-        childrenLock.lock()
-        filteredChildren =
-            try {
-                for (each in getFilteredChildren()) {
-                    sorter.apply(each)
-                }
-                val sortedChildren: List<FrameworkMethod> =
-                    ArrayList<FrameworkMethod>(getFilteredChildren())
-                Collections.sort(sortedChildren, comparator(sorter))
-                Collections.unmodifiableList(sortedChildren)
-            } finally {
-                childrenLock.unlock()
-            }
-    }
-
-    /**
-     * Implementation of [Orderable.order].
-     *
-     * @since 4.13
-     */
-    @Throws(InvalidOrderingException::class)
-    override fun order(orderer: Orderer) {
-        if (shouldNotReorder()) {
-            return
-        }
-        childrenLock.lock()
-        try {
-            var children: List<FrameworkMethod> = getFilteredChildren()
-            // In theory, we could have duplicate Descriptions. De-dup them before ordering,
-            // and add them back at the end.
-            val childMap: MutableMap<Description, MutableList<FrameworkMethod>> =
-                LinkedHashMap(children.size)
-            for (child in children) {
-                val description = describeChild(child)
-                var childrenWithDescription: MutableList<FrameworkMethod>? = childMap[description]
-                if (childrenWithDescription == null) {
-                    childrenWithDescription = ArrayList<FrameworkMethod>(1)
-                    childMap[description] = childrenWithDescription
-                }
-                childrenWithDescription.add(child)
-                orderer.apply(child)
-            }
-            val inOrder = orderer.order(childMap.keys)
-            children = ArrayList<FrameworkMethod>(children.size)
-            for (description in inOrder) {
-                children.addAll(childMap[description]!!)
-            }
-            filteredChildren = Collections.unmodifiableList(children)
-        } finally {
-            childrenLock.unlock()
-        }
-    }
-
-    private fun shouldNotReorder(): Boolean {
-        // If the test specifies a specific order, do not reorder.
-        return description.getAnnotation(FixMethodOrder::class.java) != null
-    }
-
-    private fun getFilteredChildren(): List<FrameworkMethod> {
-        childrenLock.lock()
-        val filteredChildren =
-            try {
-                if (filteredChildren != null) {
-                    filteredChildren!!
-                } else {
-                    Collections.unmodifiableList(ArrayList<FrameworkMethod>(children))
-                }
-            } finally {
-                childrenLock.unlock()
-            }
-        return filteredChildren
-    }
-
-    override fun getDescription(): Description {
-        val clazz = testClass.javaClass
-        // if subclass overrides `getName()` then we should use it
-        // to maintain backwards compatibility with JUnit 4.12
-        val description: Description =
-            if (clazz == null || clazz.name != name) {
-                Description.createSuiteDescription(name, *runnerAnnotations)
-            } else {
-                Description.createSuiteDescription(clazz, *runnerAnnotations)
-            }
-        for (child in getFilteredChildren()) {
-            description.addChild(describeChild(child))
-        }
-        return description
-    }
-
-    /**
-     * Returns a [Statement]: Call [.runChild] on each object returned by [.getChildren] (subject to
-     * any imposed filter and sort)
-     */
-    override fun childrenInvoker(notifier: RunNotifier): Statement {
-        return object : Statement() {
-            override fun evaluate() {
-                runChildren(notifier)
-            }
-        }
-    }
-
-    private fun runChildren(notifier: RunNotifier) {
-        val currentScheduler = scheduler
-        try {
-            for (each in getFilteredChildren()) {
-                currentScheduler.schedule { this.runChild(each, notifier) }
-            }
-        } finally {
-            currentScheduler.finished()
-        }
-    }
-
-    private fun comparator(sorter: Sorter): Comparator<in FrameworkMethod> {
-        return Comparator { o1, o2 -> sorter.compare(describeChild(o1), describeChild(o2)) }
-    }
-
-    /**
-     * END of code copied from ParentRunner to have local access to filteredChildren to ensure FaaS
-     * injected tests are not filtered out.
-     */
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBuilderProvider.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBuilderProvider.kt
deleted file mode 100644
index 4f9cf66..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerBuilderProvider.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.flicker.junit
-
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-@Target(
-    AnnotationTarget.FUNCTION,
-    AnnotationTarget.PROPERTY_GETTER,
-    AnnotationTarget.PROPERTY_SETTER
-)
-annotation class FlickerBuilderProvider
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerParametersRunnerFactory.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerParametersRunnerFactory.kt
deleted file mode 100644
index 75d39a5..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerParametersRunnerFactory.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.flicker.junit
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.TimestampFactory
-import com.android.server.wm.traces.parser.ANDROID_LOGGER
-import org.junit.runner.Runner
-import org.junit.runners.parameterized.ParametersRunnerFactory
-import org.junit.runners.parameterized.TestWithParameters
-
-/**
- * A {@code FlickerRunnerFactory} creates a runner for a single {@link TestWithParameters}. Parses
- * and executes assertions from a flicker DSL
- */
-class FlickerParametersRunnerFactory : ParametersRunnerFactory {
-    init {
-        CrossPlatform.setLogger(ANDROID_LOGGER)
-            .setTimestampFactory(TimestampFactory { Utils.formatRealTimestamp(it) })
-    }
-
-    override fun createRunnerForTestWithParameters(test: TestWithParameters): Runner {
-        val simpleClassName = test.testClass.javaClass.simpleName
-        val flickerTest =
-            test.parameters.filterIsInstance<FlickerTest>().firstOrNull()
-                ?: error(
-                    "Unable to extract ${FlickerTest::class.simpleName} for class $simpleClassName"
-                )
-        val scenario = flickerTest.initialize(simpleClassName)
-        val newTest =
-            TestWithParameters(
-                /*name */ "[${scenario.description}]",
-                /* testClass */ test.testClass,
-                /* parameters */ test.parameters
-            )
-        return FlickerBlockJUnit4ClassRunner(newTest, scenario)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceCachedTestCase.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceCachedTestCase.kt
deleted file mode 100644
index 2d2bde7..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceCachedTestCase.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.flicker.junit
-
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.service.FlickerServiceResultsCollector
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import java.lang.reflect.Method
-import org.junit.Assume
-import org.junit.runner.Description
-import org.junit.runner.notification.Failure
-import org.junit.runners.model.FrameworkMethod
-
-class FlickerServiceCachedTestCase(
-    method: Method,
-    scenario: IScenario,
-    internal val assertionName: String,
-    private val onlyBlocking: Boolean,
-    private val metricsCollector: FlickerServiceResultsCollector?,
-    private val isLast: Boolean
-) : FrameworkMethod(method) {
-    private val fullResults =
-        DataStore.getFlickerServiceResultsForAssertion(scenario, assertionName)
-    private val results: List<IAssertionResult>
-        get() =
-            fullResults.filter {
-                !onlyBlocking || it.assertion.stabilityGroup == AssertionInvocationGroup.BLOCKING
-            }
-
-    override fun invokeExplosively(target: Any?, vararg params: Any?): Any {
-        error("Shouldn't have reached here")
-    }
-
-    override fun getName(): String = "FaaS_$assertionName"
-
-    fun execute(description: Description) {
-        val results = results
-
-        if (isLast) {
-            metricsCollector?.testStarted(description)
-        }
-        try {
-            Assume.assumeFalse(results.isEmpty())
-            results.firstOrNull { it.assertionError != null }?.assertionError?.let { throw it }
-        } catch (e: Throwable) {
-            metricsCollector?.testFailure(Failure(description, e))
-            throw e
-        } finally {
-            if (isLast) {
-                Cache.clear()
-                metricsCollector?.testFinished(description)
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceDecorator.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceDecorator.kt
deleted file mode 100644
index 11e3b97..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/FlickerServiceDecorator.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.os.Bundle
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.helpers.IS_FAAS_ENABLED
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.service.FlickerService
-import com.android.server.wm.flicker.service.FlickerServiceResultsCollector
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.Scenario
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import org.junit.runner.Description
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-import org.junit.runners.model.TestClass
-
-class FlickerServiceDecorator(
-    testClass: TestClass,
-    scenario: Scenario?,
-    inner: IFlickerJUnitDecorator?
-) : AbstractFlickerRunnerDecorator(testClass, scenario, inner) {
-    private val arguments: Bundle = InstrumentationRegistry.getArguments()
-    private val flickerService = FlickerService()
-    private val metricsCollector: FlickerServiceResultsCollector? =
-        scenario?.let {
-            FlickerServiceResultsCollector(
-                LegacyFlickerTraceCollector(scenario),
-                collectMetricsPerTest = true
-            )
-        }
-
-    private val onlyBlocking
-        get() =
-            scenario?.getConfigValue<Boolean>(Scenario.FAAS_BLOCKING)
-                ?: arguments.getString(Scenario.FAAS_BLOCKING).toBoolean()
-
-    private val isClassFlickerServiceCompatible: Boolean
-        get() =
-            testClass.annotations.filterIsInstance<FlickerServiceCompatible>().firstOrNull() != null
-
-    override fun getChildDescription(method: FrameworkMethod?): Description? {
-        requireNotNull(scenario) { "Expected to have a scenario to run" }
-        return if (method is FlickerServiceCachedTestCase) {
-            Description.createTestDescription(
-                testClass.javaClass,
-                "${method.name}[${scenario.description}]",
-                *method.getAnnotations()
-            )
-        } else {
-            inner?.getChildDescription(method)
-        }
-    }
-
-    override fun getTestMethods(test: Any): List<FrameworkMethod> {
-        val result = inner?.getTestMethods(test)?.toMutableList() ?: mutableListOf()
-        if (shouldComputeTestMethods()) {
-            CrossPlatform.log.withTracing(
-                "$FAAS_METRICS_PREFIX getTestMethods ${testClass.javaClass.simpleName}"
-            ) {
-                result.addAll(computeFlickerServiceTests(test))
-                CrossPlatform.log.d(FLICKER_TAG, "Computed ${result.size} flicker tests")
-            }
-        }
-        return result
-    }
-
-    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
-        return object : Statement() {
-            @Throws(Throwable::class)
-            override fun evaluate() {
-                if (method is FlickerServiceCachedTestCase) {
-                    val description = getChildDescription(method) ?: error("Missing description")
-                    method.execute(description)
-                } else {
-                    inner?.getMethodInvoker(method, test)?.evaluate()
-                }
-            }
-        }
-    }
-
-    private fun shouldComputeTestMethods(): Boolean {
-        // Don't compute when called from validateInstanceMethods since this will fail
-        // as the parameters will not be set. And AndroidLogOnlyBuilder is a non-executing runner
-        // used to run tests in dry-run mode, so we don't want to execute in flicker transition in
-        // that case either.
-        val stackTrace = Thread.currentThread().stackTrace
-        val isDryRun =
-            stackTrace.any { it.methodName == "validateInstanceMethods" } ||
-                stackTrace.any {
-                    it.className == "androidx.test.internal.runner.AndroidLogOnlyBuilder"
-                } ||
-                stackTrace.any {
-                    it.className == "androidx.test.internal.runner.NonExecutingRunner"
-                }
-
-        val filters = getFiltersFromArguments()
-        // a method is filtered out if there's a filter and the filter doesn't include it's class
-        // or if the filter includes its class, but it's not flicker as a service
-        val isFilteredOut =
-            filters.isNotEmpty() && !(filters[testClass.javaClass.simpleName] ?: false)
-
-        return IS_FAAS_ENABLED &&
-            isShellTransitionsEnabled &&
-            isClassFlickerServiceCompatible &&
-            !isFilteredOut &&
-            !isDryRun
-    }
-
-    private fun getFiltersFromArguments(): Map<String, Boolean> {
-        val testFilters = arguments.getString(OPTION_NAME) ?: return emptyMap()
-        val result = mutableMapOf<String, Boolean>()
-
-        // Test the display name against all filter arguments.
-        for (testFilter in testFilters.split(",")) {
-            val filterComponents = testFilter.split("#")
-            if (filterComponents.size != 2) {
-                CrossPlatform.log.e(
-                    LOG_TAG,
-                    "Invalid filter-tests instrumentation argument supplied, $testFilter."
-                )
-                continue
-            }
-            val methodName = filterComponents[1]
-            val className = filterComponents[0]
-            result[className] = methodName.startsWith(FAAS_METRICS_PREFIX)
-        }
-
-        return result
-    }
-
-    /**
-     * Runs the flicker transition to collect the traces and run FaaS on them to get the FaaS
-     * results and then create functional test results for each of them.
-     */
-    private fun computeFlickerServiceTests(test: Any): List<FrameworkMethod> {
-        requireNotNull(scenario) { "Expected to have a scenario to run" }
-        if (!DataStore.containsFlickerServiceResult(scenario)) {
-            this.doRunFlickerService(test)
-        }
-        val aggregateResults =
-            DataStore.getFlickerServiceResults(scenario).groupBy { it.assertion.name }
-
-        val cachedResultMethod =
-            FlickerServiceCachedTestCase::class.java.getMethod("execute", Description::class.java)
-        return aggregateResults.keys.mapIndexed { idx, value ->
-            FlickerServiceCachedTestCase(
-                cachedResultMethod,
-                scenario,
-                value,
-                onlyBlocking,
-                metricsCollector,
-                isLast = aggregateResults.keys.size == idx
-            )
-        }
-    }
-
-    private fun doRunFlickerService(test: Any): List<IAssertionResult> {
-        requireNotNull(scenario) { "Expected to have a scenario to run" }
-        val description =
-            Description.createTestDescription(
-                this::class.java.simpleName,
-                "computeFlickerServiceTests"
-            )
-        this.doRunTransition(test, description)
-
-        val reader = CachedResultReader(scenario, DEFAULT_TRACE_CONFIG)
-        val results = flickerService.process(reader)
-
-        DataStore.addFlickerServiceResults(scenario, results)
-        return results
-    }
-
-    companion object {
-        private const val FAAS_METRICS_PREFIX = "FAAS"
-        private const val OPTION_NAME = "filter-tests"
-        private val LOG_TAG = FlickerServiceDecorator::class.java.simpleName
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/IFlickerJUnitDecorator.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/IFlickerJUnitDecorator.kt
deleted file mode 100644
index 97b340a..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/IFlickerJUnitDecorator.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.flicker.junit
-
-import org.junit.runner.Description
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-
-interface IFlickerJUnitDecorator {
-    fun getChildDescription(method: FrameworkMethod?): Description?
-
-    /** Returns the methods that run tests. */
-    fun getTestMethods(test: Any): List<FrameworkMethod>
-
-    fun doValidateInstanceMethods(): List<Throwable>
-
-    fun doValidateConstructor(): List<Throwable>
-
-    /** @return a [Statement] to execute a flicker service assertion */
-    fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerDecorator.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerDecorator.kt
deleted file mode 100644
index af2a14e..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerDecorator.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.flicker.junit
-
-import com.android.server.wm.traces.common.Scenario
-import org.junit.runner.Description
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-import org.junit.runners.model.TestClass
-
-class LegacyFlickerDecorator(
-    testClass: TestClass,
-    scenario: Scenario?,
-    inner: IFlickerJUnitDecorator?
-) : AbstractFlickerRunnerDecorator(testClass, scenario, inner) {
-    override fun getChildDescription(method: FrameworkMethod?): Description? {
-        return inner?.getChildDescription(method)
-    }
-
-    override fun getTestMethods(test: Any): List<FrameworkMethod> {
-        return inner?.getTestMethods(test) ?: emptyList()
-    }
-
-    override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement {
-        return object : Statement() {
-            override fun evaluate() {
-                val description = getChildDescription(method)
-                doRunTransition(test, description)
-                inner?.getMethodInvoker(method, test)?.evaluate()
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerTraceCollector.kt b/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerTraceCollector.kt
deleted file mode 100644
index 8094639..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/junit/LegacyFlickerTraceCollector.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.flicker.junit
-
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.ITracesCollector
-
-class LegacyFlickerTraceCollector(private val scenario: IScenario) : ITracesCollector {
-    override fun start() {}
-
-    override fun stop() {}
-
-    override fun getResultReader(): IReader {
-        CrossPlatform.log.d("FAAS", "LegacyFlickerTraceCollector#getCollectedTraces")
-        return CachedResultReader(scenario, DEFAULT_TRACE_CONFIG)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/EventLogMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/EventLogMonitor.kt
deleted file mode 100644
index b4ae87d..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/EventLogMonitor.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.now
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.io.TraceType
-import java.io.File
-import java.io.FileOutputStream
-
-/** Collects event logs during transitions. */
-open class EventLogMonitor : TransitionMonitor() {
-    override val traceType = TraceType.EVENT_LOG
-    final override var isEnabled = false
-        private set
-
-    private var traceStartTime: Timestamp? = null
-
-    override fun start() {
-        require(!isEnabled) { "Trace already running" }
-        isEnabled = true
-        traceStartTime = now()
-    }
-
-    override fun doStop(): File {
-        require(isEnabled) { "Trace not running" }
-        isEnabled = false
-        val sinceTime = traceStartTime?.unixNanosToLogFormat() ?: error("Missing start timestamp")
-
-        traceStartTime = null
-        val outputFile = File.createTempFile(TraceType.EVENT_LOG.fileName, "")
-
-        FileOutputStream(outputFile).use {
-            it.write("${EventLog.MAGIC_NUMBER}\n".toByteArray())
-            val command =
-                "logcat -b events -v threadtime -v printable -v uid -v nsec " +
-                    "-v epoch -t $sinceTime >> $outputFile"
-            CrossPlatform.log.d(FLICKER_TAG, "Running '$command'")
-            val eventLogString = SystemUtil.runShellCommandOrThrow(command)
-            it.write(eventLogString.toByteArray())
-        }
-
-        return outputFile
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt
deleted file mode 100644
index 33d1e11..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/Extensions.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-@file:JvmName("Extensions")
-
-package com.android.server.wm.flicker.monitor
-
-import com.android.server.wm.traces.common.DeviceTraceDump
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.parser.DeviceDumpParser
-import com.android.server.wm.traces.parser.layers.LayersTraceParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
-
-/**
- * Acquire the [WindowManagerTrace] with the device state changes that happen when executing the
- * commands defined in the [predicate].
- *
- * @param predicate Commands to execute
- * @throws UnsupportedOperationException If tracing is already activated
- */
-fun withWMTracing(predicate: () -> Unit): WindowManagerTrace {
-    return WindowManagerTraceParser().parse(WindowManagerTraceMonitor().withTracing(predicate))
-}
-
-/**
- * Acquire the [LayersTrace] with the device state changes that happen when executing the commands
- * defined in the [predicate].
- *
- * @param traceFlags Flags to indicate tracing level
- * @param predicate Commands to execute
- * @throws UnsupportedOperationException If tracing is already activated
- */
-@JvmOverloads
-fun withSFTracing(
-    traceFlags: Int = LayersTraceMonitor.TRACE_FLAGS,
-    predicate: () -> Unit
-): LayersTrace {
-    return LayersTraceParser().parse(LayersTraceMonitor(traceFlags).withTracing(predicate))
-}
-
-/**
- * Acquire the [WindowManagerTrace] and [LayersTrace] with the device state changes that happen when
- * executing the commands defined in the [predicate].
- *
- * @param predicate Commands to execute
- * @throws UnsupportedOperationException If tracing is already activated
- */
-fun withTracing(predicate: () -> Unit): DeviceTraceDump {
-    val traces = recordTraces(predicate)
-    val wmTraceData = traces.first
-    val layersTraceData = traces.second
-    return DeviceDumpParser.fromTrace(wmTraceData, layersTraceData, clearCache = true)
-}
-
-/**
- * Acquire the [WindowManagerTrace] and [LayersTrace] with the device state changes that happen when
- * executing the commands defined in the [predicate].
- *
- * @param predicate Commands to execute
- * @throws UnsupportedOperationException If tracing is already activated
- * @return a pair containing the WM and SF traces
- */
-fun recordTraces(predicate: () -> Unit): Pair<ByteArray, ByteArray> {
-    var wmTraceData = ByteArray(0)
-    val layersTraceData =
-        LayersTraceMonitor().withTracing {
-            wmTraceData = WindowManagerTraceMonitor().withTracing(predicate)
-        }
-
-    return Pair(wmTraceData, layersTraceData)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt
deleted file mode 100644
index 4f4e7b3..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.monitor
-
-import com.android.server.wm.flicker.io.ResultWriter
-
-/** Collects test artifacts during a UI transition. */
-interface ITransitionMonitor {
-    /** Starts monitor. */
-    fun start()
-
-    /** Stops monitor and writes the result to a [ResultWriter] */
-    fun stop(writer: ResultWriter)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
deleted file mode 100644
index 92e39ed..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.view.WindowManagerGlobal
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.io.WINSCOPE_EXT
-import com.android.server.wm.traces.common.layers.LayersTrace
-import java.io.File
-
-/** Captures [LayersTrace] from SurfaceFlinger. */
-open class LayersTraceMonitor @JvmOverloads constructor(private val traceFlags: Int = TRACE_FLAGS) :
-    TransitionMonitor() {
-    private val windowManager = WindowManagerGlobal.getWindowManagerService()
-    override val traceType = TraceType.SF
-    override val isEnabled
-        get() = windowManager.isLayerTracing
-
-    override fun start() {
-        windowManager.setLayerTracingFlags(traceFlags)
-        windowManager.isLayerTracing = true
-    }
-
-    override fun doStop(): File {
-        windowManager.isLayerTracing = false
-        return TRACE_DIR.resolve("layers_trace$WINSCOPE_EXT")
-    }
-
-    companion object {
-        const val TRACE_FLAGS = 0x47 // TRACE_CRITICAL|TRACE_INPUT|TRACE_COMPOSITION|TRACE_SYNC
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/NoTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/NoTraceMonitor.kt
deleted file mode 100644
index 61fadca..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/NoTraceMonitor.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.flicker.monitor
-
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.traces.common.CrossPlatform
-
-/**
- * A monitor that doesn't actually collect any traces and instead get the resultSetter sets the
- * trace file directly when called.
- */
-class NoTraceMonitor(private val resultSetter: (ResultWriter) -> Unit) : ITransitionMonitor {
-    override fun start() {
-        // Does nothing
-    }
-
-    override fun stop(writer: ResultWriter) {
-        writer.setTransitionStartTime(CrossPlatform.timestamp.min())
-        writer.setTransitionEndTime(CrossPlatform.timestamp.max())
-        this.resultSetter.invoke(writer)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecorder.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecorder.kt
deleted file mode 100644
index de27555..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecorder.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.content.Context
-import android.os.SystemClock
-import androidx.annotation.VisibleForTesting
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.io.TraceType
-import java.io.File
-
-/** Captures screen contents and saves it as a mp4 video file. */
-open class ScreenRecorder
-@JvmOverloads
-constructor(
-    private val context: Context,
-    private val outputFile: File = File.createTempFile("transition", "screen_recording"),
-    private val width: Int = 720,
-    private val height: Int = 1280
-) : TraceMonitor() {
-    override val traceType = TraceType.SCREEN_RECORDING
-
-    private var recordingThread: Thread? = null
-    private var recordingRunnable: ScreenRecordingRunnable? = null
-
-    private fun newRecordingThread(): Thread {
-        val runnable = ScreenRecordingRunnable(outputFile, context, width, height)
-        recordingRunnable = runnable
-        return Thread(runnable)
-    }
-
-    /** Indicates if any frame has been recorded. */
-    @VisibleForTesting
-    val isFrameRecorded: Boolean
-        get() =
-            when {
-                !isEnabled -> false
-                else -> recordingRunnable?.isFrameRecorded ?: false
-            }
-
-    override fun start() {
-        require(recordingThread == null) { "Screen recorder already running" }
-
-        val recordingThread = newRecordingThread()
-        this.recordingThread = recordingThread
-        CrossPlatform.log.d(FLICKER_TAG, "Starting screen recording thread")
-        recordingThread.start()
-
-        var remainingTime = WAIT_TIMEOUT_MS
-        do {
-            SystemClock.sleep(WAIT_INTERVAL_MS)
-            remainingTime -= WAIT_INTERVAL_MS
-        } while (recordingRunnable?.isFrameRecorded != true)
-
-        require(outputFile.exists()) { "Screen recorder didn't start" }
-    }
-
-    override fun doStop(): File {
-        require(recordingThread != null) { "Screen recorder was not started" }
-
-        CrossPlatform.log.d(FLICKER_TAG, "Stopping screen recording. Storing result in $outputFile")
-        try {
-            recordingRunnable?.stop()
-            recordingThread?.join()
-        } catch (e: Exception) {
-            throw IllegalStateException("Unable to stop screen recording", e)
-        } finally {
-            recordingRunnable = null
-            recordingThread = null
-        }
-        return outputFile
-    }
-
-    override val isEnabled
-        get() = recordingThread != null
-
-    override fun toString(): String {
-        return "ScreenRecorder($outputFile)"
-    }
-
-    companion object {
-        private const val WAIT_TIMEOUT_MS = 5000L
-        private const val WAIT_INTERVAL_MS = 500L
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecordingRunnable.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecordingRunnable.kt
deleted file mode 100644
index 442804f..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/ScreenRecordingRunnable.kt
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.flicker.monitor
-
-import android.content.Context
-import android.hardware.display.DisplayManager
-import android.media.MediaCodec
-import android.media.MediaCodecInfo
-import android.media.MediaFormat
-import android.media.MediaMuxer
-import android.os.SystemClock
-import android.util.DisplayMetrics
-import android.view.WindowManager
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import java.io.File
-import java.io.FileOutputStream
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.concurrent.TimeUnit
-
-/** Runnable to record the screen contents and winscope metadata */
-class ScreenRecordingRunnable(
-    private val outputFile: File,
-    context: Context,
-    private val width: Int = 720,
-    private val height: Int = 1280
-) : Runnable {
-    private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
-    private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-    private var finished = false
-    internal var isFrameRecorded = false
-
-    private val metrics: DisplayMetrics
-        get() {
-            val metrics = DisplayMetrics()
-            windowManager.defaultDisplay.getRealMetrics(metrics)
-            return metrics
-        }
-
-    private val encoder = createEncoder()
-    private val inputSurface = encoder.createInputSurface()
-    private val virtualDisplay =
-        displayManager.createVirtualDisplay(
-            "Recording Display",
-            width,
-            height,
-            metrics.densityDpi,
-            inputSurface,
-            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
-            null,
-            null
-        )
-    private val muxer = createMuxer()
-    private var metadataTrackIndex = -1
-    private var videoTrackIndex = -1
-
-    internal fun stop() {
-        encoder.signalEndOfInputStream()
-        finished = true
-    }
-
-    override fun run() {
-        CrossPlatform.log.d(FLICKER_TAG, "Starting screen recording to file $outputFile")
-
-        val timestampsUs = mutableListOf<Long>()
-        try {
-            // Start encoder and muxer
-            encoder.start()
-            val bufferInfo = MediaCodec.BufferInfo()
-
-            while (true) {
-                val bufferIndex = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_MS)
-                if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    prepareMuxer()
-                } else if (bufferIndex >= 0) {
-                    val elapsedTimeUs = writeSample(bufferIndex, bufferInfo)
-                    val endOfStream = bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
-                    // end of the stream samples have 0 timestamp
-                    if (endOfStream > 0) {
-                        break
-                    } else {
-                        timestampsUs.add(elapsedTimeUs)
-                    }
-                }
-            }
-        } finally {
-            writeMetadata(timestampsUs)
-            encoder.stop()
-            muxer.stop()
-            muxer.release()
-            encoder.release()
-            inputSurface.release()
-            virtualDisplay.release()
-        }
-    }
-
-    /**
-     * Fetches a sample from the encoder and writes it to the video file
-     *
-     * @return sample timestamp (or 0 for invalid buffers)
-     */
-    private fun writeSample(bufferIndex: Int, bufferInfo: MediaCodec.BufferInfo): Long {
-        val data = encoder.getOutputBuffer(bufferIndex)
-        return if (data != null) {
-            val endOfStream = bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
-
-            if (endOfStream == 0) {
-                val outputBuffer =
-                    encoder.getOutputBuffer(bufferIndex) ?: error("Unable to acquire next frame")
-
-                muxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo)
-                isFrameRecorded = true
-            }
-            encoder.releaseOutputBuffer(bufferIndex, /* render */ false)
-            bufferInfo.presentationTimeUs
-        } else {
-            0
-        }
-    }
-
-    private fun prepareMuxer() {
-        videoTrackIndex = muxer.addTrack(encoder.outputFormat)
-        val metadataFormat = MediaFormat()
-        metadataFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE_METADATA)
-        metadataTrackIndex = muxer.addTrack(metadataFormat)
-        muxer.start()
-    }
-
-    /**
-     * Saves metadata needed by Winscope to synchronize the screen recording playback with other
-     * traces.
-     *
-     * The metadata (version 2) is written as a binary array with the following format:
-     * - winscope magic string (#VV1NSC0PET1ME2#, 16B).
-     * - the metadata version number (4B little endian).
-     * - Realtime-to-elapsed time offset in nanoseconds (8B little endian).
-     * - the recorded frames count (4B little endian)
-     * - for each recorded frame:
-     * ```
-     *     - System time in elapsed clock timebase in nanoseconds (8B little endian).
-     * ```
-     */
-    private fun writeMetadata(timestampsUs: List<Long>) {
-        if (timestampsUs.isEmpty()) {
-            CrossPlatform.log.v(FLICKER_TAG, "Not writing winscope metadata (no frames/timestamps)")
-            return
-        }
-
-        CrossPlatform.log.v(
-            FLICKER_TAG,
-            "Writing winscope metadata (size=${timestampsUs.size} " +
-                "(timestamps [us] = ${timestampsUs.first()}-${timestampsUs.last()})"
-        )
-
-        val timeOffsetNs =
-            TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) -
-                SystemClock.elapsedRealtimeNanos()
-
-        val bufferSize =
-            WINSCOPE_MAGIC_STRING.toByteArray().size +
-                Int.SIZE_BYTES +
-                Long.SIZE_BYTES +
-                Int.SIZE_BYTES +
-                (timestampsUs.size * Long.SIZE_BYTES)
-
-        val buffer =
-            ByteBuffer.allocate(bufferSize)
-                .order(ByteOrder.LITTLE_ENDIAN)
-                .put(WINSCOPE_MAGIC_STRING.toByteArray())
-                .putInt(WINSCOPE_METADATA_VERSION)
-                .putLong(timeOffsetNs)
-                .putInt(timestampsUs.size)
-                .apply { timestampsUs.forEach { putLong(TimeUnit.MICROSECONDS.toNanos(it)) } }
-
-        val bufferInfo = MediaCodec.BufferInfo()
-        bufferInfo.size = bufferSize
-        bufferInfo.presentationTimeUs = timestampsUs[0]
-        muxer.writeSampleData(metadataTrackIndex, buffer, bufferInfo)
-    }
-
-    /**
-     * Create and configure a MediaCodec encoder with [MIME_TYPE_VIDEO] format.
-     *
-     * @return a Surface that can be used to record
-     */
-    private fun createEncoder(): MediaCodec {
-        val format = MediaFormat.createVideoFormat(MIME_TYPE_VIDEO, width, height)
-        val displayMode = windowManager.defaultDisplay.mode
-        format.setInteger(
-            MediaFormat.KEY_COLOR_FORMAT,
-            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
-        )
-        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
-        format.setFloat(MediaFormat.KEY_FRAME_RATE, displayMode.refreshRate)
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL)
-        format.setInteger(MediaFormat.KEY_WIDTH, width)
-        format.setInteger(MediaFormat.KEY_HEIGHT, height)
-        format.setString(MediaFormat.KEY_MIME, MIME_TYPE_VIDEO)
-
-        val mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE_VIDEO)
-        mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
-        return mediaCodec
-    }
-
-    private fun createMuxer(): MediaMuxer {
-        outputFile.deleteIfExists()
-        require(!outputFile.exists())
-        outputFile.createNewFile()
-        val inputStream = FileOutputStream(outputFile)
-        return MediaMuxer(inputStream.fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
-    }
-
-    companion object {
-        private const val WINSCOPE_MAGIC_STRING = "#VV1NSC0PET1ME2#"
-        private const val WINSCOPE_METADATA_VERSION = 2
-        private const val MIME_TYPE_VIDEO = MediaFormat.MIMETYPE_VIDEO_AVC
-        private const val MIME_TYPE_METADATA = "application/octet-stream"
-        private const val BIT_RATE = 2000000 // 2Mbps
-        private const val IFRAME_INTERVAL = 2 // 2 second between I-frames
-        private const val TIMEOUT_MS = 100L
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt
deleted file mode 100644
index b13f597..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TraceMonitor.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.traces.common.io.TraceType
-import java.io.File
-
-/**
- * Base class for monitors containing common logic to read the trace as a byte array and save the
- * trace to another location.
- */
-abstract class TraceMonitor : ITransitionMonitor {
-    abstract val isEnabled: Boolean
-    abstract val traceType: TraceType
-    protected abstract fun doStop(): File
-
-    /** Stops monitor. */
-    final override fun stop(writer: ResultWriter) {
-        val artifact =
-            try {
-                val srcFile = doStop()
-                moveTraceFileToTmpDir(srcFile)
-            } catch (e: Throwable) {
-                throw RuntimeException("Could not stop trace", e)
-            }
-        writer.addTraceResult(traceType, artifact)
-    }
-
-    private fun moveTraceFileToTmpDir(sourceFile: File): File {
-        val newFile = File.createTempFile(sourceFile.name, "")
-        Utils.moveFile(sourceFile, newFile)
-        require(newFile.exists()) { "Unable to save trace file $newFile" }
-        return newFile
-    }
-
-    companion object {
-        @JvmStatic protected val TRACE_DIR = File("/data/misc/wmtrace/")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransactionsTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransactionsTraceMonitor.kt
deleted file mode 100644
index 1bbccec..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransactionsTraceMonitor.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.view.WindowManagerGlobal
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.io.WINSCOPE_EXT
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import java.io.File
-
-/** Captures [TransactionsTrace] from SurfaceFlinger. */
-open class TransactionsTraceMonitor : TransitionMonitor() {
-    private val windowManager = WindowManagerGlobal.getWindowManagerService()
-    override val isEnabled = true
-    override val traceType = TraceType.TRANSACTION
-
-    override fun start() {
-        windowManager.setActiveTransactionTracing(true)
-    }
-
-    override fun doStop(): File {
-        windowManager.setActiveTransactionTracing(false)
-        return TRACE_DIR.resolve("transactions_trace$WINSCOPE_EXT")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt
deleted file mode 100644
index df706ec..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionMonitor.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.traces.common.ScenarioBuilder
-import java.io.File
-
-abstract class TransitionMonitor : TraceMonitor() {
-    /**
-     * Acquires the trace generated when executing the commands defined in the [predicate].
-     *
-     * @param predicate Commands to execute
-     * @throws UnsupportedOperationException If tracing is already activated
-     */
-    fun withTracing(predicate: () -> Unit): ByteArray {
-        if (this.isEnabled) {
-            throw UnsupportedOperationException(
-                "Trace already running. " + "This is likely due to chained 'withTracing' calls."
-            )
-        }
-        val result: IResultData
-        try {
-            this.start()
-            predicate()
-        } finally {
-            val writer = createWriter()
-            this.stop(writer)
-            result = writer.write()
-        }
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val bytes = reader.readBytes(traceType) ?: error("Missing trace $traceType")
-        result.artifact.deleteIfExists()
-        return bytes
-    }
-
-    private fun createWriter(): ResultWriter {
-        val className = this::class.simpleName ?: error("Missing class name for $this")
-        val scenario = ScenarioBuilder().forClass(className).build()
-        val tmpDir = File.createTempFile("withTracing", className).parentFile
-        return ResultWriter().forScenario(scenario).withOutputDir(tmpDir)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionsTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionsTraceMonitor.kt
deleted file mode 100644
index 11e30d3..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/TransitionsTraceMonitor.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.view.WindowManagerGlobal
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.io.WINSCOPE_EXT
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import java.io.File
-
-/** Captures [TransitionsTrace] from SurfaceFlinger. */
-open class TransitionsTraceMonitor : TransitionMonitor() {
-    private val windowManager = WindowManagerGlobal.getWindowManagerService()
-    override val traceType = TraceType.TRANSITION
-    override val isEnabled
-        get() = windowManager.isTransitionTraceEnabled
-
-    override fun start() {
-        windowManager.startTransitionTrace()
-    }
-
-    override fun doStop(): File {
-        windowManager.stopTransitionTrace()
-        return TRACE_DIR.resolve("transition_trace$WINSCOPE_EXT")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
deleted file mode 100644
index a0b128e..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.view.WindowManagerGlobal
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import java.io.File
-
-/** Captures [WindowManagerTrace] from WindowManager. */
-open class WindowManagerTraceMonitor : TransitionMonitor() {
-    private val windowManager = WindowManagerGlobal.getWindowManagerService()
-    override val traceType = TraceType.WM
-    override val isEnabled
-        get() = windowManager.isWindowTraceEnabled
-
-    override fun start() {
-        windowManager.startWindowTrace()
-    }
-
-    override fun doStop(): File {
-        windowManager.stopWindowTrace()
-        return TRACE_DIR.resolve(TraceType.WM.fileName)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/ChangeDisplayOrientationRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/ChangeDisplayOrientationRule.kt
deleted file mode 100644
index 88ac799..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/rules/ChangeDisplayOrientationRule.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.rules
-
-import android.app.Instrumentation
-import android.content.Context
-import android.os.RemoteException
-import android.view.WindowManager
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/**
- * Changes display orientation before a test
- *
- * @param targetOrientation Target orientation
- * @param instrumentation Instrumentation mechanism to use
- * @param clearCacheAfterParsing If the caching used while parsing the proto should be
- * ```
- *                               cleared or remain in memory
- * ```
- */
-data class ChangeDisplayOrientationRule
-@JvmOverloads
-constructor(
-    private val targetOrientation: PlatformConsts.Rotation,
-    private val resetOrientationAfterTest: Boolean = true,
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-    private val clearCacheAfterParsing: Boolean = true
-) : TestWatcher() {
-    private var initialOrientation = PlatformConsts.Rotation.ROTATION_0
-
-    override fun starting(description: Description?) {
-        CrossPlatform.log.withTracing("ChangeDisplayOrientationRule:starting") {
-            CrossPlatform.log.v(
-                FLICKER_TAG,
-                "Changing display orientation to " +
-                    "$targetOrientation ${targetOrientation.description}"
-            )
-            val wm =
-                instrumentation.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-            initialOrientation = PlatformConsts.Rotation.getByValue(wm.defaultDisplay.rotation)
-            setRotation(targetOrientation, instrumentation, clearCacheAfterParsing)
-        }
-    }
-
-    override fun finished(description: Description?) {
-        CrossPlatform.log.withTracing("ChangeDisplayOrientationRule:finished") {
-            if (resetOrientationAfterTest) {
-                setRotation(initialOrientation, instrumentation, clearCacheAfterParsing)
-            }
-        }
-    }
-
-    companion object {
-        @JvmOverloads
-        fun setRotation(
-            rotation: PlatformConsts.Rotation,
-            instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-            clearCacheAfterParsing: Boolean = true,
-            wmHelper: WindowManagerStateHelper =
-                WindowManagerStateHelper(
-                    instrumentation,
-                    clearCacheAfterParsing = clearCacheAfterParsing
-                )
-        ) {
-            val device: UiDevice = UiDevice.getInstance(instrumentation)
-
-            try {
-                when (rotation) {
-                    PlatformConsts.Rotation.ROTATION_270 -> device.setOrientationRight()
-                    PlatformConsts.Rotation.ROTATION_90 -> device.setOrientationLeft()
-                    PlatformConsts.Rotation.ROTATION_0 -> device.setOrientationNatural()
-                    else -> device.setOrientationNatural()
-                }
-
-                if (wmHelper.currentState.wmState.canRotate) {
-                    wmHelper.StateSyncBuilder().withRotation(rotation).waitForAndVerify()
-                } else {
-                    wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-                    CrossPlatform.log.v(FLICKER_TAG, "Rotation is not allowed in the state")
-                    return
-                }
-
-                // During seamless rotation the app window is shown
-                val currWmState = wmHelper.currentState.wmState
-                if (currWmState.visibleWindows.none { it.isFullscreen }) {
-                    wmHelper
-                        .StateSyncBuilder()
-                        .withNavOrTaskBarVisible()
-                        .withStatusBarVisible()
-                        .waitForAndVerify()
-                }
-            } catch (e: RemoteException) {
-                throw RuntimeException(e)
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt
deleted file mode 100644
index 7c68181..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/rules/LaunchAppRule.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.rules
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/**
- * Launches an app before the test
- *
- * @param instrumentation Instrumentation mechanism to use
- * @param wmHelper WM/SF synchronization helper
- * @param appHelper App to launch
- * @param clearCacheAfterParsing If the caching used while parsing the proto should be
- * ```
- *                               cleared or remain in memory
- * ```
- */
-class LaunchAppRule
-@JvmOverloads
-constructor(
-    private val appHelper: StandardAppHelper,
-    private val instrumentation: Instrumentation = appHelper.mInstrumentation,
-    private val clearCacheAfterParsing: Boolean = true,
-    private val wmHelper: WindowManagerStateHelper =
-        WindowManagerStateHelper(clearCacheAfterParsing = clearCacheAfterParsing)
-) : TestWatcher() {
-    @JvmOverloads
-    constructor(
-        componentMatcher: ComponentNameMatcher,
-        appName: String = "",
-        instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-        clearCache: Boolean = true,
-        wmHelper: WindowManagerStateHelper =
-            WindowManagerStateHelper(clearCacheAfterParsing = clearCache)
-    ) : this(
-        StandardAppHelper(instrumentation, appName, componentMatcher),
-        instrumentation,
-        clearCache,
-        wmHelper
-    )
-
-    override fun starting(description: Description?) {
-        CrossPlatform.log.withTracing("LaunchAppRule:finished") {
-            CrossPlatform.log.v(FLICKER_TAG, "Launching app $appHelper")
-            appHelper.launchViaIntent()
-            appHelper.exit(wmHelper)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/rules/RemoveAllTasksButHomeRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/rules/RemoveAllTasksButHomeRule.kt
deleted file mode 100644
index 099e976..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/rules/RemoveAllTasksButHomeRule.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.rules
-
-import android.app.ActivityTaskManager
-import android.app.WindowConfiguration
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/** Test rule to ensure no tasks as running before executing the test */
-class RemoveAllTasksButHomeRule() : TestWatcher() {
-    override fun starting(description: Description?) {
-        CrossPlatform.log.withTracing("RemoveAllTasksButHomeRule:finished") {
-            CrossPlatform.log.v(FLICKER_TAG, "Removing all tasks (except home)")
-            removeAllTasksButHome()
-        }
-    }
-
-    companion object {
-        @JvmStatic
-        fun removeAllTasksButHome() {
-            val atm = ActivityTaskManager.getService()
-            atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
-        }
-
-        private val ALL_ACTIVITY_TYPE_BUT_HOME =
-            intArrayOf(
-                WindowConfiguration.ACTIVITY_TYPE_STANDARD,
-                WindowConfiguration.ACTIVITY_TYPE_ASSISTANT,
-                WindowConfiguration.ACTIVITY_TYPE_RECENTS,
-                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
-            )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/Consts.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/Consts.kt
deleted file mode 100644
index a4ccb91..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/Consts.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.flicker.runner
-
-import com.android.server.wm.traces.common.FLICKER_TAG
-
-internal const val FLICKER_RUNNER_TAG = "$FLICKER_TAG-Runner"
-const val EMPTY_TRANSITIONS_ERROR = "A flicker test must include transitions to run"
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/ExecutionError.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/ExecutionError.kt
deleted file mode 100644
index 937469c..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/ExecutionError.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.flicker.runner
-
-/**
- * Base class for flicker execution failures
- *
- * @param inner cause
- */
-open class ExecutionError(private val inner: Throwable) : Throwable(inner) {
-    init {
-        super.setStackTrace(inner.stackTrace)
-    }
-
-    override val message = inner.toString()
-
-    override val cause = inner.cause ?: super.cause
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/SetupTeardownRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/SetupTeardownRule.kt
deleted file mode 100644
index a9d33e4..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/SetupTeardownRule.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import android.platform.test.rule.ArtifactSaver
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Test rule to run transition setup and teardown
- *
- * @param flicker test definition
- * @param resultWriter to write
- * @param scenario to run the transition
- * @param instrumentation to interact with the device
- * @param setupCommands to run before the transition
- * @param teardownCommands to run after the transition
- * @param wmHelper to stabilize the UI before/after transitions
- */
-class SetupTeardownRule(
-    private val flicker: IFlickerTestData,
-    private val resultWriter: ResultWriter,
-    private val scenario: IScenario,
-    private val instrumentation: Instrumentation,
-    private val setupCommands: List<IFlickerTestData.() -> Any> = flicker.transitionSetup,
-    private val teardownCommands: List<IFlickerTestData.() -> Any> = flicker.transitionTeardown,
-    private val wmHelper: WindowManagerStateHelper = flicker.wmHelper
-) : TestRule {
-    override fun apply(base: Statement?, description: Description?): Statement {
-        return object : Statement() {
-            override fun evaluate() {
-                try {
-                    doRunTransitionSetup(description)
-                    base?.evaluate()
-                } finally {
-                    doRunTransitionTeardown(description)
-                }
-            }
-        }
-    }
-
-    @Throws(TransitionSetupFailure::class)
-    private fun doRunTransitionSetup(description: Description?) {
-        CrossPlatform.log.withTracing("doRunTransitionSetup") {
-            Utils.notifyRunnerProgress(scenario, "Running transition setup for $description")
-            try {
-                setupCommands.forEach { it.invoke(flicker) }
-                Utils.doWaitForUiStabilize(wmHelper)
-            } catch (e: Throwable) {
-                ArtifactSaver.onError(Utils.expandDescription(description, "setup"), e)
-                throw TransitionSetupFailure(e)
-            }
-        }
-    }
-
-    @Throws(TransitionTeardownFailure::class)
-    private fun doRunTransitionTeardown(description: Description?) {
-        CrossPlatform.log.withTracing("doRunTransitionTeardown") {
-            Utils.notifyRunnerProgress(scenario, "Running transition teardown for $description")
-            try {
-                teardownCommands.forEach { it.invoke(flicker) }
-                Utils.doWaitForUiStabilize(wmHelper)
-            } catch (e: Throwable) {
-                ArtifactSaver.onError(Utils.expandDescription(description, "teardown"), e)
-                throw TransitionTeardownFailure(e)
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TraceMonitorRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TraceMonitorRule.kt
deleted file mode 100644
index d4db193..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TraceMonitorRule.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import android.platform.test.rule.ArtifactSaver
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Test rule to start and stop trace monitors and update [resultWriter]
- *
- * @param traceMonitors to collect device data
- * @param scenario to run the transition
- * @param wmHelper to stabilize the UI before/after transitions
- * @param resultWriter to write
- * @param instrumentation to interact with the device
- */
-class TraceMonitorRule(
-    private val traceMonitors: List<ITransitionMonitor>,
-    private val scenario: IScenario,
-    private val wmHelper: WindowManagerStateHelper,
-    private val resultWriter: ResultWriter,
-    private val instrumentation: Instrumentation
-) : TestRule {
-    override fun apply(base: Statement?, description: Description?): Statement {
-        return object : Statement() {
-            override fun evaluate() {
-                try {
-                    doStartMonitors(description)
-                    base?.evaluate()
-                } finally {
-                    doStopMonitors(description)
-                }
-            }
-        }
-    }
-
-    private fun doStartMonitors(description: Description?) {
-        CrossPlatform.log.withTracing("doStartMonitors") {
-            Utils.notifyRunnerProgress(scenario, "Starting traces for $description")
-            traceMonitors.forEach {
-                try {
-                    it.start()
-                } catch (e: Throwable) {
-                    ArtifactSaver.onError(Utils.expandDescription(description, "startTrace"), e)
-                    throw TransitionTracingFailure(e)
-                }
-            }
-        }
-    }
-
-    private fun doStopMonitors(description: Description?) {
-        CrossPlatform.log.withTracing("doStopMonitors") {
-            Utils.notifyRunnerProgress(scenario, "Stopping traces for $description")
-            val errors =
-                traceMonitors.map {
-                    runCatching {
-                        try {
-                            it.stop(resultWriter)
-                        } catch (e: Throwable) {
-                            ArtifactSaver.onError(
-                                Utils.expandDescription(description, "stopTrace"),
-                                e
-                            )
-                            CrossPlatform.log.e(FLICKER_TAG, "Unable to stop $it", e)
-                            throw TransitionTracingFailure(e)
-                        }
-                    }
-                }
-            errors.firstOrNull { it.isFailure }?.exceptionOrNull()?.let { throw it }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionFailure.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionFailure.kt
deleted file mode 100644
index eb28ff2..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionFailure.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.flicker.runner
-
-/**
- * Exception type for errors occurred during the transition
- *
- * @param inner cause
- */
-class TransitionExecutionFailure(inner: Throwable) : ExecutionError(inner) {
-    override val message = "Transition execution failed: ${super.message}"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionRule.kt
deleted file mode 100644
index b578868..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionExecutionRule.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import android.platform.test.rule.ArtifactSaver
-import android.util.EventLog
-import com.android.server.wm.flicker.FlickerTag
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.now
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.parser.getCurrentState
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import java.io.File
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Test rule to execute the transition and update [resultWriter]
- *
- * @param flicker test definition
- * @param resultWriter to write
- * @param scenario to run the transition
- * @param instrumentation to interact with the device
- * @param commands to run during the transition
- * @param wmHelper to stabilize the UI before/after transitions
- */
-class TransitionExecutionRule(
-    private val flicker: IFlickerTestData,
-    private val resultWriter: ResultWriter,
-    private val scenario: IScenario,
-    private val instrumentation: Instrumentation = flicker.instrumentation,
-    private val commands: List<IFlickerTestData.() -> Any> = flicker.transitions,
-    private val wmHelper: WindowManagerStateHelper = flicker.wmHelper
-) : TestRule {
-    private var tags = mutableSetOf<String>()
-
-    override fun apply(base: Statement?, description: Description?): Statement {
-        return object : Statement() {
-            override fun evaluate() {
-                CrossPlatform.log.withTracing("transition") {
-                    try {
-                        Utils.notifyRunnerProgress(scenario, "Running transition $description")
-                        doRunBeforeTransition()
-                        commands.forEach { it.invoke(flicker) }
-                        base?.evaluate()
-                    } catch (e: Throwable) {
-                        ArtifactSaver.onError(Utils.expandDescription(description, "transition"), e)
-                        throw if (e is AssertionError) {
-                            e
-                        } else {
-                            TransitionExecutionFailure(e)
-                        }
-                    } finally {
-                        doRunAfterTransition()
-                    }
-                }
-            }
-        }
-    }
-
-    private fun doRunBeforeTransition() {
-        CrossPlatform.log.withTracing("doRunBeforeTransition") {
-            Utils.notifyRunnerProgress(scenario, "Running doRunBeforeTransition")
-            CrossPlatform.log.d(FLICKER_RUNNER_TAG, "doRunBeforeTransition")
-            val now = now()
-            resultWriter.setTransitionStartTime(now)
-            EventLog.writeEvent(
-                FlickerTag.TRANSITION_START,
-                now.unixNanos,
-                now.elapsedNanos,
-                now.systemUptimeNanos
-            )
-            flicker.setCreateTagListener { doCreateTag(it) }
-            doValidate()
-        }
-    }
-
-    private fun doRunAfterTransition() {
-        CrossPlatform.log.withTracing("doRunAfterTransition") {
-            Utils.notifyRunnerProgress(scenario, "Running doRunAfterTransition")
-            CrossPlatform.log.d(FLICKER_RUNNER_TAG, "doRunAfterTransition")
-            Utils.doWaitForUiStabilize(wmHelper)
-            val now = now()
-            resultWriter.setTransitionEndTime(now)
-            EventLog.writeEvent(
-                FlickerTag.TRANSITION_END,
-                now.unixNanos,
-                now.elapsedNanos,
-                now.systemUptimeNanos
-            )
-            flicker.clearTagListener()
-        }
-    }
-
-    private fun doValidate() {
-        require(commands.isNotEmpty()) { EMPTY_TRANSITIONS_ERROR }
-    }
-
-    private fun doValidateTag(tag: String) {
-        require(!tags.contains(tag)) { "Tag `$tag` already used" }
-        require(!tag.contains(" ")) { "Tag can't contain spaces, instead it was `$tag`" }
-        require(!tag.contains("__")) { "Tag can't `__``, instead it was `$tag`" }
-    }
-
-    private fun doCreateTag(tag: String) {
-        CrossPlatform.log.withTracing("doRunAfterTransition") {
-            Utils.notifyRunnerProgress(scenario, "Creating tag $tag")
-            doValidateTag(tag)
-            tags.add(tag)
-
-            val deviceStateBytes = getCurrentState(instrumentation.uiAutomation)
-            val wmDumpFile = File.createTempFile(TraceType.WM_DUMP.fileName, tag)
-            val layersDumpFile = File.createTempFile(TraceType.SF_DUMP.fileName, tag)
-
-            wmDumpFile.writeBytes(deviceStateBytes.first)
-            layersDumpFile.writeBytes(deviceStateBytes.second)
-
-            resultWriter.addTraceResult(TraceType.WM_DUMP, wmDumpFile, tag)
-            resultWriter.addTraceResult(TraceType.SF_DUMP, layersDumpFile, tag)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionRunner.kt
deleted file mode 100644
index 44d45b2..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionRunner.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import android.platform.test.rule.NavigationModeRule
-import android.platform.test.rule.PressHomeRule
-import android.platform.test.rule.UnlockScreenRule
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.datastore.CachedResultWriter
-import com.android.server.wm.flicker.helpers.MessagingAppHelper
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
-import com.android.server.wm.flicker.rules.LaunchAppRule
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import org.junit.rules.RuleChain
-import org.junit.runner.Description
-
-/**
- * Transition runner that executes a default device setup (based on [scenario]) as well as the
- * flicker setup/transition/teardown
- */
-class TransitionRunner(
-    private val scenario: IScenario,
-    private val instrumentation: Instrumentation,
-    private val resultWriter: ResultWriter = CachedResultWriter()
-) {
-    /** Executes [flicker] transition and returns the result */
-    fun execute(flicker: IFlickerTestData, description: Description?): IResultData {
-        return CrossPlatform.log.withTracing("TransitionRunner:execute") {
-            resultWriter.forScenario(scenario).withOutputDir(flicker.outputDir)
-
-            val ruleChain = buildTestRuleChain(flicker)
-            try {
-                ruleChain.apply(/* statement */ null, description).evaluate()
-                resultWriter.setRunComplete()
-            } catch (e: Throwable) {
-                resultWriter.setRunFailed(e)
-            }
-            resultWriter.write()
-        }
-    }
-
-    /**
-     * Create the default flicker test setup rules. In order:
-     * - unlock device
-     * - change orientation
-     * - change navigation mode
-     * - launch an app
-     * - remove all apps
-     * - go home
-     *
-     * (b/186740751) An app should be launched because, after changing the navigation mode, the
-     * first app launch is handled as a screen size change (similar to a rotation), this causes
-     * different problems during testing (e.g. IME now shown on app launch)
-     */
-    private fun buildTestRuleChain(flicker: IFlickerTestData): RuleChain {
-        return RuleChain.outerRule(UnlockScreenRule())
-            .around(
-                NavigationModeRule(
-                    scenario.navBarMode.value,
-                    /* changeNavigationModeAfterTest */ false
-                )
-            )
-            .around(
-                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
-            )
-            .around(RemoveAllTasksButHomeRule())
-            .around(
-                ChangeDisplayOrientationRule(
-                    scenario.startRotation,
-                    resetOrientationAfterTest = false,
-                    clearCacheAfterParsing = false
-                )
-            )
-            .around(PressHomeRule())
-            .around(
-                TraceMonitorRule(
-                    flicker.traceMonitors,
-                    scenario,
-                    flicker.wmHelper,
-                    resultWriter,
-                    instrumentation
-                )
-            )
-            .around(SetupTeardownRule(flicker, resultWriter, scenario, instrumentation))
-            .around(TransitionExecutionRule(flicker, resultWriter, scenario, instrumentation))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionSetupFailure.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionSetupFailure.kt
deleted file mode 100644
index dcae354..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionSetupFailure.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.flicker.runner
-
-/**
- * Exception type for errors occurred during the transition setup
- *
- * @param inner cause
- */
-class TransitionSetupFailure(inner: Throwable) : ExecutionError(inner) {
-    override val message = "Setup failed: ${super.message}"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTeardownFailure.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTeardownFailure.kt
deleted file mode 100644
index a9b637a..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTeardownFailure.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.flicker.runner
-
-/**
- * Exception type for errors occurred during the transition teardown
- *
- * @param inner cause
- */
-class TransitionTeardownFailure(inner: Throwable) : ExecutionError(inner) {
-    override val message = "Teardown failed: ${super.message}"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTracingFailure.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTracingFailure.kt
deleted file mode 100644
index e619408..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/TransitionTracingFailure.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.flicker.runner
-
-/**
- * Exception type for errors occurred during tracing
- *
- * @param inner cause
- */
-class TransitionTracingFailure(inner: Throwable) : ExecutionError(inner) {
-    override val message = "Tracing failed: ${super.message}"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/runner/Utils.kt b/libraries/flicker/src/com/android/server/wm/flicker/runner/Utils.kt
deleted file mode 100644
index 3427339..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/runner/Utils.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import android.os.Bundle
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.common.ConditionList
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.runner.Description
-
-/** Helper class for flicker transition rules */
-object Utils {
-    /**
-     * Conditions that determine when the UI is in a stable and no windows or layers are animating
-     * or changing state.
-     */
-    private val UI_STABLE_CONDITIONS =
-        ConditionList(
-            listOf(
-                WindowManagerConditionsFactory.isWMStateComplete(),
-                WindowManagerConditionsFactory.hasLayersAnimating().negate()
-            )
-        )
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-
-    internal fun doWaitForUiStabilize(wmHelper: WindowManagerStateHelper) {
-        wmHelper.StateSyncBuilder().add(UI_STABLE_CONDITIONS).waitFor()
-    }
-
-    internal fun notifyRunnerProgress(scenario: IScenario, msg: String) {
-        CrossPlatform.log.d(FLICKER_RUNNER_TAG, "${scenario.key} - $msg")
-        val results = Bundle()
-        results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "$msg\n")
-        instrumentation.sendStatus(1, results)
-    }
-
-    internal fun expandDescription(description: Description?, suffix: String): Description? =
-        Description.createTestDescription(
-            description?.className,
-            "${description?.displayName}-$suffix",
-            description?.annotations?.toTypedArray()
-        )
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt
deleted file mode 100644
index 94090dd..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerService.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.service
-
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.errors.ErrorTrace
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.IFlickerService
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import com.android.server.wm.traces.common.service.assertors.factories.AssertionFactory
-import com.android.server.wm.traces.common.service.assertors.factories.CombinedAssertionFactory
-import com.android.server.wm.traces.common.service.assertors.factories.GeneratedAssertionsFactory
-import com.android.server.wm.traces.common.service.assertors.factories.IAssertionFactory
-import com.android.server.wm.traces.common.service.assertors.runners.AssertionRunner
-import com.android.server.wm.traces.common.service.assertors.runners.IAssertionRunner
-import com.android.server.wm.traces.common.service.config.FlickerServiceConfig
-import com.android.server.wm.traces.common.service.extractors.CombinedScenarioExtractor
-import com.android.server.wm.traces.common.service.extractors.IScenarioExtractor
-
-/** Contains the logic for Flicker as a Service. */
-class FlickerService(
-    val scenarioExtractor: IScenarioExtractor =
-        CombinedScenarioExtractor(FlickerServiceConfig.getExtractors()),
-    val assertionFactory: IAssertionFactory =
-        CombinedAssertionFactory(listOf(AssertionFactory(), GeneratedAssertionsFactory())),
-    val assertionRunner: IAssertionRunner = AssertionRunner(),
-) : IFlickerService {
-    /**
-     * The entry point for WM Flicker Service.
-     *
-     * Calls the Tagging Engine and the Assertion Engine.
-     *
-     * @param reader A flicker trace reader
-     * @return A pair with an [ErrorTrace] and a map that associates assertion names with 0 if it
-     * fails and 1 if it passes
-     */
-    override fun process(reader: IReader): List<IAssertionResult> {
-        return CrossPlatform.log.withTracing("FlickerService#process") {
-            try {
-                require(isShellTransitionsEnabled) {
-                    "Shell transitions must be enabled for FaaS to work!"
-                }
-
-                val scenarioInstances = scenarioExtractor.extract(reader)
-                val assertions =
-                    scenarioInstances.flatMap { assertionFactory.generateAssertionsFor(it) }
-                assertionRunner.execute(assertions)
-            } catch (exception: Throwable) {
-                CrossPlatform.log.e("$FLICKER_TAG-ASSERT", "FAILED PROCESSING", exception)
-                throw exception
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollector.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollector.kt
deleted file mode 100644
index c813d8b..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollector.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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.flicker.service
-
-import android.app.Instrumentation
-import android.device.collectors.BaseMetricListener
-import android.device.collectors.DataRecord
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.annotations.VisibleForTesting
-import com.android.server.wm.flicker.runner.ExecutionError
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-import com.android.server.wm.traces.common.service.IFlickerService
-import com.android.server.wm.traces.common.service.ITracesCollector
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import org.junit.runner.Description
-import org.junit.runner.Result
-import org.junit.runner.notification.Failure
-
-/**
- * Collects all the Flicker Service's metrics which are then uploaded for analysis and monitoring to
- * the CrystalBall database.
- */
-class FlickerServiceResultsCollector(
-    private val tracesCollector: ITracesCollector,
-    private val flickerService: IFlickerService = FlickerService(),
-    instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-    private val collectMetricsPerTest: Boolean = true,
-    private val reportOnlyForPassingTests: Boolean = true
-) : BaseMetricListener(), IFlickerServiceResultsCollector {
-    private var hasFailedTest = false
-    private var testSkipped = false
-
-    private val _executionErrors = mutableListOf<ExecutionError>()
-    override val executionErrors
-        get() = _executionErrors
-
-    @VisibleForTesting val assertionResults = mutableListOf<IAssertionResult>()
-    @VisibleForTesting
-    val assertionResultsByTest = mutableMapOf<Description, List<IAssertionResult>>()
-
-    init {
-        setInstrumentation(instrumentation)
-    }
-
-    override fun onTestRunStart(runData: DataRecord, description: Description) {
-        errorReportingBlock {
-            CrossPlatform.log.i(
-                LOG_TAG,
-                "onTestRunStart :: collectMetricsPerTest = $collectMetricsPerTest"
-            )
-            if (!collectMetricsPerTest) {
-                hasFailedTest = false
-                tracesCollector.start()
-            }
-        }
-    }
-
-    override fun onTestStart(testData: DataRecord, description: Description) {
-        errorReportingBlock {
-            CrossPlatform.log.i(
-                LOG_TAG,
-                "onTestStart :: collectMetricsPerTest = $collectMetricsPerTest"
-            )
-            if (collectMetricsPerTest) {
-                hasFailedTest = false
-                tracesCollector.start()
-            }
-            testSkipped = false
-        }
-    }
-
-    override fun onTestFail(testData: DataRecord, description: Description, failure: Failure) {
-        errorReportingBlock {
-            CrossPlatform.log.i(LOG_TAG, "onTestFail")
-            hasFailedTest = true
-        }
-    }
-
-    override fun testSkipped(description: Description) {
-        errorReportingBlock {
-            CrossPlatform.log.i(LOG_TAG, "testSkipped")
-            testSkipped = true
-        }
-    }
-
-    override fun onTestEnd(testData: DataRecord, description: Description) {
-        errorReportingBlock {
-            CrossPlatform.log.i(
-                LOG_TAG,
-                "onTestEnd :: collectMetricsPerTest = $collectMetricsPerTest"
-            )
-            if (collectMetricsPerTest && !testSkipped) {
-                stopTracingAndCollectFlickerMetrics(testData, description)
-            }
-        }
-    }
-
-    override fun onTestRunEnd(runData: DataRecord, result: Result) {
-        errorReportingBlock {
-            CrossPlatform.log.i(
-                LOG_TAG,
-                "onTestRunEnd :: collectMetricsPerTest = $collectMetricsPerTest"
-            )
-            if (!collectMetricsPerTest) {
-                stopTracingAndCollectFlickerMetrics(runData)
-            }
-        }
-    }
-
-    private fun stopTracingAndCollectFlickerMetrics(
-        dataRecord: DataRecord,
-        description: Description? = null
-    ) {
-        CrossPlatform.log.i(LOG_TAG, "Stopping trace collection")
-        tracesCollector.stop()
-        CrossPlatform.log.i(LOG_TAG, "Stopped trace collection")
-        if (reportOnlyForPassingTests && hasFailedTest) {
-            return
-        }
-
-        val reader = tracesCollector.getResultReader()
-        dataRecord.addStringMetric(WINSCOPE_FILE_PATH_KEY, reader.artifactPath)
-        CrossPlatform.log.i(LOG_TAG, "Processing traces")
-        val results = flickerService.process(reader)
-        CrossPlatform.log.i(LOG_TAG, "Got ${results.size} results")
-        assertionResults.addAll(results)
-        if (description != null) {
-            require(assertionResultsByTest[description] == null) {
-                "Test description already contains flicker assertion results."
-            }
-            assertionResultsByTest[description] = results
-        }
-        val aggregatedResults = processFlickerResults(results)
-        collectMetrics(dataRecord, aggregatedResults)
-    }
-
-    private fun processFlickerResults(
-        results: List<IAssertionResult>
-    ): Map<String, AggregatedFlickerResult> {
-        val aggregatedResults = mutableMapOf<String, AggregatedFlickerResult>()
-        for (result in results) {
-            val key = getKeyForAssertionResult(result)
-            if (!aggregatedResults.containsKey(key)) {
-                aggregatedResults[key] = AggregatedFlickerResult()
-            }
-            aggregatedResults[key]!!.addResult(result)
-        }
-        return aggregatedResults
-    }
-
-    private fun collectMetrics(
-        data: DataRecord,
-        aggregatedResults: Map<String, AggregatedFlickerResult>
-    ) {
-        val it = aggregatedResults.entries.iterator()
-
-        while (it.hasNext()) {
-            val (key, result) = it.next()
-            CrossPlatform.log.v(LOG_TAG, "Adding metric ${key}_FAILURES = ${result.failures}")
-            data.addStringMetric("${key}_FAILURES", "${result.failures}")
-        }
-    }
-
-    private fun getKeyForAssertionResult(result: IAssertionResult): String {
-        return "$FAAS_METRICS_PREFIX::${result.assertion.name}"
-    }
-
-    private fun errorReportingBlock(function: () -> Unit) {
-        try {
-            function()
-        } catch (e: Throwable) {
-            CrossPlatform.log.e(FLICKER_TAG, "Error executing in FlickerServiceResultsCollector", e)
-            _executionErrors.add(ExecutionError(e))
-        }
-    }
-
-    override fun testContainsFlicker(description: Description): Boolean {
-        val resultsForTest = resultsForTest(description)
-        return resultsForTest.any { it.failed }
-    }
-
-    override fun resultsForTest(description: Description): List<IAssertionResult> {
-        val resultsForTest = assertionResultsByTest[description]
-        requireNotNull(resultsForTest) { "No results set for test $description" }
-        return resultsForTest
-    }
-
-    companion object {
-        // Unique prefix to add to all FaaS metrics to identify them
-        private const val FAAS_METRICS_PREFIX = "FAAS"
-        private const val LOG_TAG = "$FLICKER_TAG-Collector"
-        private const val WINSCOPE_FILE_PATH_KEY = "winscope_file_path"
-
-        class AggregatedFlickerResult {
-            var failures = 0
-            var passes = 0
-            val errors = mutableListOf<String>()
-            var invocationGroup: AssertionInvocationGroup? = null
-
-            fun addResult(result: IAssertionResult) {
-                if (result.failed) {
-                    failures++
-                    errors.add(result.assertionError?.message ?: "FAILURE WITHOUT ERROR MESSAGE...")
-                } else {
-                    passes++
-                }
-
-                if (invocationGroup == null) {
-                    invocationGroup = result.assertion.stabilityGroup
-                }
-
-                if (invocationGroup != result.assertion.stabilityGroup) {
-                    error("Unexpected assertion group mismatch")
-                }
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollector.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollector.kt
deleted file mode 100644
index e46b4fd..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollector.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.flicker.service
-
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.io.ResultReaderWithLru
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.monitor.EventLogMonitor
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor
-import com.android.server.wm.flicker.monitor.TransactionsTraceMonitor
-import com.android.server.wm.flicker.monitor.TransitionsTraceMonitor
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.Scenario
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.ITracesCollector
-import java.io.File
-
-class FlickerServiceTracesCollector(
-    val outputDir: File,
-    val scenario: Scenario =
-        ScenarioBuilder().forClass(FlickerServiceTracesCollector::class.java.simpleName).build()
-) : ITracesCollector {
-
-    private var result: IResultData? = null
-
-    private val traceMonitors =
-        listOf(
-            WindowManagerTraceMonitor(),
-            LayersTraceMonitor(),
-            TransitionsTraceMonitor(),
-            TransactionsTraceMonitor(),
-            EventLogMonitor()
-        )
-
-    override fun start() {
-        reportErrorsBlock("Failed to start traces") {
-            reset()
-            traceMonitors.forEach { it.start() }
-        }
-    }
-
-    override fun stop() {
-        reportErrorsBlock("Failed to stop traces") {
-            CrossPlatform.log.v(LOG_TAG, "Creating output directory for trace files")
-            outputDir.mkdirs()
-
-            CrossPlatform.log.v(LOG_TAG, "Stopping trace monitors")
-            val writer = ResultWriter().forScenario(scenario).withOutputDir(outputDir)
-            traceMonitors.forEach { it.stop(writer) }
-            result = writer.write()
-        }
-    }
-
-    override fun getResultReader(): IReader {
-        return reportErrorsBlock("Failed to get collected traces") {
-            val result = result
-            requireNotNull(result) { "Result not set" }
-            ResultReaderWithLru(result, DEFAULT_TRACE_CONFIG)
-        }
-    }
-
-    private fun reset() {
-        result = null
-        cleanupTraceFiles()
-    }
-
-    /**
-     * Remove the WM trace and layers trace files collected from previous test runs if the directory
-     * exists.
-     */
-    private fun cleanupTraceFiles() {
-        if (outputDir.exists()) {
-            outputDir.deleteRecursively()
-        }
-    }
-
-    private fun <T : Any> reportErrorsBlock(msg: String, block: () -> T): T {
-        try {
-            return block()
-        } catch (e: Throwable) {
-            CrossPlatform.log.e(LOG_TAG, msg, e)
-            throw e
-        }
-    }
-
-    companion object {
-        private const val LOG_TAG = "$FLICKER_TAG-Collector"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/IFlickerServiceResultsCollector.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/IFlickerServiceResultsCollector.kt
deleted file mode 100644
index c8f7f35..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/IFlickerServiceResultsCollector.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.flicker.service
-
-import com.android.server.wm.flicker.runner.ExecutionError
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import org.junit.runner.Description
-import org.junit.runner.notification.Failure
-
-interface IFlickerServiceResultsCollector {
-    val executionErrors: List<ExecutionError>
-    fun testStarted(description: Description)
-    fun testFailure(failure: Failure)
-    fun testSkipped(description: Description)
-    fun testFinished(description: Description)
-    fun testContainsFlicker(description: Description): Boolean
-    fun resultsForTest(description: Description): List<IAssertionResult>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/layers_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/layers_trace.winscope
deleted file mode 100644
index cfaffff..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/layers_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope
deleted file mode 100644
index 7d05abb..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transition_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transition_trace.winscope
deleted file mode 100644
index fa87a54..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/transition_trace.winscope
+++ /dev/null
@@ -1,41 +0,0 @@
-	TRNTRACEùù=ÿÿÿÿÿÿÿÿÿ¿à–ÛÇ(ÿÿÿÿÿÿÿÿÿ‡í×ÐÛdz‡ø­íéÇ(:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:@
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B 
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivityć¥üâˆÈ(@Ô±€ ÚHÕ±€ Ú: 
-®‘ñTask:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:%
-!Ï̐^ð±ÿÿÿÿÿÿÿWindowContainer:B
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!Òü•kð±ÿÿÿÿÿÿÿWindowContainer:K 
-CÏì˜v<com.google.android.apps.nexuslauncher/.NexusLauncherActivity:K 
-CÜíx<com.google.android.apps.nexuslauncher/.NexusLauncherActivity´‡ÞхÉ(: 
-®‘ñTask:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:%
-!Ï̐^ð±ÿÿÿÿÿÿÿWindowContainer:B
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!Òü•kð±ÿÿÿÿÿÿÿWindowContainer:K 
-CÏì˜v<com.google.android.apps.nexuslauncher/.NexusLauncherActivity:K 
-CÜíx<com.google.android.apps.nexuslauncher/.NexusLauncherActivity
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/wm_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/wm_trace.winscope
deleted file mode 100644
index 0dd3b9c..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/AppLaunch/ROTATION_0/trace1/wm_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/Common/trace1/transactions_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/Common/trace1/transactions_trace.winscope
deleted file mode 100644
index e69de29..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/Common/trace1/transactions_trace.winscope
+++ /dev/null
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/temp.txt b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/temp.txt
deleted file mode 100644
index f2ba8f8..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config/temp.txt
+++ /dev/null
@@ -1 +0,0 @@
-abc
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace.winscope
deleted file mode 100644
index cfaffff..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace_configuration.json b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace_configuration.json
deleted file mode 100644
index 7c2d4ca..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/layers_trace_configuration.json
+++ /dev/null
@@ -1,8 +0,0 @@
-[
-  {
-    "componentToTypeMap": {
-      "openingLayerName_toBeCompleted": "OPENING_APP",
-      "closingLayerName_toBeCompleted": "CLOSING_APP"
-    }
-  }
-]
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope
deleted file mode 100644
index 7d05abb..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transactions_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transition_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transition_trace.winscope
deleted file mode 100644
index fa87a54..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/transition_trace.winscope
+++ /dev/null
@@ -1,41 +0,0 @@
-	TRNTRACEùù=ÿÿÿÿÿÿÿÿÿ¿à–ÛÇ(ÿÿÿÿÿÿÿÿÿ‡í×ÐÛdz‡ø­íéÇ(:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:@
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B 
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivityć¥üâˆÈ(@Ô±€ ÚHÕ±€ Ú: 
-®‘ñTask:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:%
-!Ï̐^ð±ÿÿÿÿÿÿÿWindowContainer:B
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!Òü•kð±ÿÿÿÿÿÿÿWindowContainer:K 
-CÏì˜v<com.google.android.apps.nexuslauncher/.NexusLauncherActivity:K 
-CÜíx<com.google.android.apps.nexuslauncher/.NexusLauncherActivity´‡ÞхÉ(: 
-®‘ñTask:' 
-!ÄüŽ
-ð±ÿÿÿÿÿÿÿWindowContainer:' 
-!üÊÚð±ÿÿÿÿÿÿÿWindowContainer:' 
-!›Ïçð±ÿÿÿÿÿÿÿWindowContainer:' 
-!ó»Þð±ÿÿÿÿÿÿÿWindowContainer:' 
-!×Æøð±ÿÿÿÿÿÿÿWindowContainer:' !
-!ö†”'ð±ÿÿÿÿÿÿÿWindowContainer:%
-!Ï̐^ð±ÿÿÿÿÿÿÿWindowContainer:B
-<åŽë_5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!©šØhð±ÿÿÿÿÿÿÿWindowContainer:B
-<áéÕj5com.android.server.wm.flicker.testapp/.SimpleActivity:' 
-!Òü•kð±ÿÿÿÿÿÿÿWindowContainer:K 
-CÏì˜v<com.google.android.apps.nexuslauncher/.NexusLauncherActivity:K 
-CÜíx<com.google.android.apps.nexuslauncher/.NexusLauncherActivity
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/wm_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/wm_trace.winscope
deleted file mode 100644
index 0dd3b9c..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/AppLaunch/ROTATION_0/trace1/wm_trace.winscope
+++ /dev/null
Binary files differ
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/Common/trace1/transactions_trace.winscope b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/Common/trace1/transactions_trace.winscope
deleted file mode 100644
index e69de29..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/Common/trace1/transactions_trace.winscope
+++ /dev/null
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/temp.txt b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/temp.txt
deleted file mode 100644
index f2ba8f8..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/temp.txt
+++ /dev/null
@@ -1 +0,0 @@
-abc
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/traces_for_scenarios.json b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/traces_for_scenarios.json
deleted file mode 100644
index 8d2c6f9..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/assertiongenerator_config_test/traces_for_scenarios.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "AppLaunch": 1,
-  "Common": 2
-}
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/temp.txt b/libraries/flicker/src/com/android/server/wm/flicker/service/resources/temp.txt
deleted file mode 100644
index dda6eb4..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/resources/temp.txt
+++ /dev/null
@@ -1 +0,0 @@
-abcdefg
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/service/rules/FlickerServiceRule.kt b/libraries/flicker/src/com/android/server/wm/flicker/service/rules/FlickerServiceRule.kt
deleted file mode 100644
index 0002e40..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/service/rules/FlickerServiceRule.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.flicker.service.rules
-
-import android.platform.test.rule.TestWatcher
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.service.FlickerServiceResultsCollector
-import com.android.server.wm.flicker.service.FlickerServiceTracesCollector
-import com.android.server.wm.flicker.service.IFlickerServiceResultsCollector
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.FLICKER_TAG
-import com.android.server.wm.traces.common.TimestampFactory
-import com.android.server.wm.traces.parser.ANDROID_LOGGER
-import org.junit.AssumptionViolatedException
-import org.junit.runner.Description
-import org.junit.runner.notification.Failure
-
-/**
- * A test rule that runs Flicker as a Service on the tests this rule is applied to.
- *
- * Note there are performance implications to using this test rule in tests. Tracing will be enabled
- * during the test which will slow down everything. So if the test is performance critical then an
- * alternative should be used.
- *
- * @see TODO for examples on how to use this test rule in your own tests
- */
-open class FlickerServiceRule
-@JvmOverloads
-constructor(
-    private val metricsCollector: IFlickerServiceResultsCollector =
-        FlickerServiceResultsCollector(
-            tracesCollector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir()),
-            instrumentation = InstrumentationRegistry.getInstrumentation()
-        ),
-    // defaults to true
-    private val failTestOnFaasFailure: Boolean =
-        InstrumentationRegistry.getArguments().getString("faas:blocking")?.let { it.toBoolean() }
-            ?: true
-) : TestWatcher() {
-
-    init {
-        CrossPlatform.setLogger(ANDROID_LOGGER)
-            .setTimestampFactory(TimestampFactory { Utils.formatRealTimestamp(it) })
-    }
-
-    /** Invoked when a test is about to start */
-    public override fun starting(description: Description) {
-        CrossPlatform.log.i(LOG_TAG, "Test starting $description")
-        metricsCollector.testStarted(description)
-    }
-
-    /** Invoked when a test succeeds */
-    public override fun succeeded(description: Description) {
-        CrossPlatform.log.i(LOG_TAG, "Test succeeded $description")
-    }
-
-    /** Invoked when a test fails */
-    public override fun failed(e: Throwable?, description: Description) {
-        CrossPlatform.log.e(LOG_TAG, "$description test failed  with $e")
-        metricsCollector.testFailure(Failure(description, e))
-    }
-
-    /** Invoked when a test is skipped due to a failed assumption. */
-    public override fun skipped(e: AssumptionViolatedException, description: Description) {
-        CrossPlatform.log.i(LOG_TAG, "Test skipped $description with $e")
-        metricsCollector.testSkipped(description)
-    }
-
-    /** Invoked when a test method finishes (whether passing or failing) */
-    public override fun finished(description: Description) {
-        CrossPlatform.log.i(LOG_TAG, "Test finished $description")
-        metricsCollector.testFinished(description)
-        if (metricsCollector.executionErrors.isNotEmpty()) {
-            for (executionError in metricsCollector.executionErrors) {
-                CrossPlatform.log.e(LOG_TAG, "FaaS reported execution errors", executionError)
-            }
-            throw metricsCollector.executionErrors[0]
-        }
-        if (failTestOnFaasFailure && metricsCollector.testContainsFlicker(description)) {
-            throw metricsCollector.resultsForTest(description).first { it.failed }.assertionError
-                ?: error("Unexpectedly missing assertion error")
-        }
-    }
-
-    companion object {
-        const val LOG_TAG = "$FLICKER_TAG-ServiceRule"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/proto/errors.proto b/libraries/flicker/src/com/android/server/wm/proto/errors.proto
deleted file mode 100644
index 94427bb..0000000
--- a/libraries/flicker/src/com/android/server/wm/proto/errors.proto
+++ /dev/null
@@ -1,35 +0,0 @@
-syntax = "proto2";
-
-package com.android.server.wm.flicker;
-
-// Each message has its own class file created.
-option java_multiple_files = true;
-
-message FlickerErrorProto {
-  required string stacktrace = 1;
-  required string message = 2;
-  optional int32 layerId = 3;
-  optional string windowToken = 4;
-  optional int32 taskId = 5;
-  optional string assertionName = 6;
-}
-
-message FlickerErrorStateProto {
-  required int64 timestamp = 1;
-  repeated FlickerErrorProto errors = 2;
-}
-
-message FlickerErrorTraceProto {
-  /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 |
-     MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits
-     and there's no nice way to put 64bit constants into .proto files. */
-  enum MagicNumber {
-    INVALID = 0;
-    MAGIC_NUMBER_L = 0x54525245; /* ERRT (little-endian ASCII) */
-    MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
-  }
-
-  /* Must be the first field, set to value in MagicNumber */
-  optional fixed64 magic_number = 1;
-  repeated FlickerErrorStateProto states = 2;
-}
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/proto/tags.proto b/libraries/flicker/src/com/android/server/wm/proto/tags.proto
deleted file mode 100644
index dd352b5..0000000
--- a/libraries/flicker/src/com/android/server/wm/proto/tags.proto
+++ /dev/null
@@ -1,45 +0,0 @@
-syntax = "proto2";
-
-package com.android.server.wm.flicker;
-
-// Each message has its own class file created.
-option java_multiple_files = true;
-
-message FlickerTagProto {
-  enum Transition {
-    ROTATION = 0;
-    APP_LAUNCH = 1;
-    APP_CLOSE = 2;
-    IME_APPEAR = 3;
-    IME_DISAPPEAR = 4;
-    PIP_ENTER = 5;
-    PIP_RESIZE = 6;
-    PIP_EXIT = 7;
-  };
-  required bool isStartTag = 1;
-  required Transition transition = 2;
-  required int32 id = 3;
-  optional int32 layerId = 4;
-  optional string windowToken = 5;
-  optional int32 taskId = 6;
-}
-
-message FlickerTagStateProto {
-  required int64 timestamp = 1;
-  repeated FlickerTagProto tags = 2;
-}
-
-message FlickerTagTraceProto {
-  /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 |
-     MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits
-     and there's no nice way to put 64bit constants into .proto files. */
-  enum MagicNumber {
-    INVALID = 0;
-    MAGIC_NUMBER_L = 0x54474154; /* TAGT (little-endian ASCII) */
-    MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
-  }
-
-  /* Must be the first field, set to value in MagicNumber */
-  optional fixed64 magic_number = 1;
-  repeated FlickerTagStateProto states = 2;
-}
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ActiveBuffer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ActiveBuffer.kt
deleted file mode 100644
index d15f298..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ActiveBuffer.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for ActiveBufferProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
- *
- * This class is used by flicker and Winscope
- */
-class ActiveBuffer
-private constructor(width: Int = 0, height: Int = 0, val stride: Int = 0, val format: Int = 0) :
-    Size(width, height) {
-    override fun prettyPrint(): String = "w:$width, h:$height, stride:$stride, format:$format"
-
-    override fun equals(other: Any?): Boolean =
-        other is ActiveBuffer &&
-            other.height == height &&
-            other.width == width &&
-            other.stride == stride &&
-            other.format == format
-
-    override fun hashCode(): Int {
-        var result = height
-        result = 31 * result + width
-        result = 31 * result + stride
-        result = 31 * result + format
-        return result
-    }
-
-    override fun toString(): String = prettyPrint()
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: ActiveBuffer
-            get() = withCache { ActiveBuffer() }
-        @JsName("from")
-        fun from(width: Int, height: Int, stride: Int, format: Int): ActiveBuffer = withCache {
-            ActiveBuffer(width, height, stride, format)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/AssertionTag.kt b/libraries/flicker/src/com/android/server/wm/traces/common/AssertionTag.kt
deleted file mode 100644
index 2631c9d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/AssertionTag.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-/**
- * Identify a trace location. By default all traces have: [START], [END] and [ALL] locations,
- * representing inital, final and all trace states.
- *
- * In addition, it is possible to create custom trace locations (tags).
- */
-object AssertionTag {
-    const val START = "start"
-    const val END = "end"
-    const val ALL = "all"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/BaseElement.kt b/libraries/flicker/src/com/android/server/wm/traces/common/BaseElement.kt
deleted file mode 100644
index bf6e907..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/BaseElement.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.traces.common
-
-import kotlin.js.JsName
-
-interface BaseElement<ChildType> {
-    @JsName("children") val children: Array<ChildType>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Cache.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Cache.kt
deleted file mode 100644
index c26e5c6..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Cache.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.traces.common
-
-import kotlin.js.JsName
-
-object Cache {
-    @JsName("cache") private val cache = mutableMapOf<Any, Any>()
-
-    @JsName("get")
-    fun <T : Any> get(element: T): T {
-        return cache.getOrPut(element) { element } as T
-    }
-
-    @JsName("clear")
-    fun clear() {
-        cache.clear()
-    }
-}
-
-@JsName("withCache")
-inline fun <reified T : Any> withCache(newInstancePredicate: () -> T): T =
-    Cache.get(newInstancePredicate())
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt
deleted file mode 100644
index 1f93e8f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Color.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for ColorProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
- *
- * This class is used by flicker and Winscope
- */
-class Color private constructor(r: Float, g: Float, b: Float, val a: Float) : Color3(r, g, b) {
-    override val isEmpty: Boolean
-        get() = a == 0f || r < 0 || g < 0 || b < 0
-
-    override val isNotEmpty: Boolean
-        get() = !isEmpty
-
-    @JsName("isOpaque") val isOpaque: Boolean = a == 1.0f
-
-    override fun prettyPrint(): String {
-        val parentPrint = super.prettyPrint()
-        return "$parentPrint a:$a"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Color) return false
-        if (!super.equals(other)) return false
-
-        if (a != other.a) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + a.hashCode()
-        return result
-    }
-
-    companion object {
-        val EMPTY: Color
-            get() = withCache { Color(r = -1f, g = -1f, b = -1f, a = 0f) }
-        val DEFAULT: Color
-            get() = withCache { Color(r = 0f, g = 0f, b = 0f, a = 1f) }
-
-        fun from(r: Float, g: Float, b: Float, a: Float): Color = withCache { Color(r, g, b, a) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Color3.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Color3.kt
deleted file mode 100644
index 3c92bd5..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Color3.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for Color3 (frameworks/native/services/surfaceflinger/layerproto/transactions.proto)
- *
- * This class is used by flicker and Winscope
- */
-open class Color3(val r: Float, val g: Float, val b: Float) {
-    @JsName("isEmpty")
-    open val isEmpty: Boolean
-        get() = r < 0 || g < 0 || b < 0
-
-    @JsName("isNotEmpty")
-    open val isNotEmpty: Boolean
-        get() = !isEmpty
-
-    @JsName("prettyPrint")
-    open fun prettyPrint(): String {
-        val r = FloatFormatter.format(r)
-        val g = FloatFormatter.format(g)
-        val b = FloatFormatter.format(b)
-        return "r:$r g:$g b:$b"
-    }
-
-    override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Color3) return false
-
-        if (r != other.r) return false
-        if (g != other.g) return false
-        if (b != other.b) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = r.hashCode()
-        result = 31 * result + g.hashCode()
-        result = 31 * result + b.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Color3
-            get() = withCache { Color3(r = -1f, g = -1f, b = -1f) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt
deleted file mode 100644
index 591eb7f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Condition.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * The utility class to wait a condition with customized options. The default retry policy is 5
- * times with interval 1 second.
- *
- * @param <T> The type of the object to validate.
- *
- * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
- * ```
- *     println("Success");
- * ```
- * } // Wait for customized result with customized validation. String result =
- * Condition.waitForResult(new Condition<String>("string comparison")
- * ```
- *         .setResultSupplier(() -> "Result string")
- *         .setResultValidator(str -> str.equals("Expected string"))
- *         .setRetryIntervalMs(500)
- *         .setRetryLimit(3)
- *         .setOnFailure(str -> println("Failed on " + str)));
- * ```
- * </pre>
- *
- * @param message The message to show what is waiting for.
- * @param condition If it returns true, that means the condition is satisfied.
- */
-open class Condition<T>(
-    @JsName("message") protected open val message: String = "",
-    @JsName("condition") protected open val condition: (T) -> Boolean
-) {
-    /** @return if [value] satisfies the condition */
-    @JsName("isSatisfied")
-    fun isSatisfied(value: T): Boolean {
-        return condition.invoke(value)
-    }
-
-    /** @return the negation of the current assertion */
-    @JsName("negate")
-    fun negate(): Condition<T> = Condition(message = "!$message") { !this.condition.invoke(it) }
-
-    /** @return a formatted message for the passing or failing condition on a state */
-    @JsName("getMessage")
-    open fun getMessage(value: T): String = "$message(passed=${isSatisfied(value)})"
-
-    override fun toString(): String = this.message
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt
deleted file mode 100644
index 1784e9d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ConditionList.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * The utility class to validate a set of conditions
- *
- * This class is used to easily integrate multiple conditions into a single verification, for
- * example, during [WaitCondition], while keeping the individual conditions separate for better
- * reuse
- *
- * @param conditions conditions to be checked
- */
-class ConditionList<T>(@JsName("conditions") val conditions: List<Condition<T>>) :
-    Condition<T>("", { false }) {
-    constructor(vararg conditions: Condition<T>) : this(listOf(*conditions))
-
-    override val message: String
-        get() {
-            return "(\n${
-                conditions
-                    .joinToString(" and \n") { it.toString() }
-                    .prependIndent("    ")
-            }\n)"
-        }
-
-    override val condition: (T) -> Boolean
-        get() = { conditions.all { condition -> condition.isSatisfied(it) } }
-
-    override fun getMessage(value: T): String {
-        return "(\n${
-            conditions
-                .joinToString(" and \n") { it.getMessage(value) }
-                .prependIndent("    ")
-        }\n)"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Consts.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Consts.kt
deleted file mode 100644
index 1d664d7..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Consts.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-const val FLICKER_TAG = "FLICKER"
-const val MILLISECOND_AS_NANOSECONDS: Long = 1000000
-const val SECOND_AS_NANOSECONDS: Long = 1000000000
-const val MINUTE_AS_NANOSECONDS: Long = 60000000000
-const val HOUR_AS_NANOSECONDS: Long = 3600000000000
-const val DAY_AS_NANOSECONDS: Long = 86400000000000
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/CrossPlatform.kt b/libraries/flicker/src/com/android/server/wm/traces/common/CrossPlatform.kt
deleted file mode 100644
index 2719d6c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/CrossPlatform.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-object CrossPlatform {
-    var log: ILogger = LoggerBuilder().build()
-        private set
-    var timestamp: TimestampFactory = TimestampFactory()
-        private set
-
-    fun setLogger(logger: ILogger) = apply { log = logger }
-    fun setTimestampFactory(factory: TimestampFactory) = apply { timestamp = factory }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt
deleted file mode 100644
index 914dee6..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceStateDump.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-
-/**
- * Represents a state dump containing the [WindowManagerState] and the [BaseLayerTraceEntry] both
- * parsed.
- */
-class DeviceStateDump(
-    override val wmState: WindowManagerState,
-    override val layerState: BaseLayerTraceEntry
-) : NullableDeviceStateDump(wmState, layerState)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt b/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt
deleted file mode 100644
index 675d348..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/DeviceTraceDump.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import kotlin.js.JsName
-
-/**
- * Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed and
- * in raw (byte) data.
- *
- * @param wmTrace Parsed [WindowManagerTrace]
- * @param layersTrace Parsed [LayersTrace]
- * @param transactionsTrace Parse [TransactionsTrace]
- * @param transitionsTrace Parsed [TransitionsTrace]
- * @param eventLog Parsed [EventLog]
- */
-class DeviceTraceDump(
-    @JsName("wmTrace") val wmTrace: WindowManagerTrace?,
-    @JsName("layersTrace") val layersTrace: LayersTrace?,
-    @JsName("transactionsTrace") val transactionsTrace: TransactionsTrace? = null,
-    @JsName("transitionsTrace") val transitionsTrace: TransitionsTrace? = null,
-    @JsName("eventLog") val eventLog: EventLog? = null,
-) {
-    /** A deviceTraceDump is considered valid if at least one of the layers/wm traces is non-null */
-    val isValid: Boolean
-        get() {
-            if (wmTrace == null && layersTrace == null) {
-                return false
-            }
-            return true
-        }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/FloatFormatter.kt b/libraries/flicker/src/com/android/server/wm/traces/common/FloatFormatter.kt
deleted file mode 100644
index 34d73d7..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/FloatFormatter.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * A formatter to print floats with up to 3 decimal digits.
- *
- * This is necessary because multiplatform kotlin projects don't support String.format yet (issue
- * KT-21644)
- */
-object FloatFormatter {
-    @JsName("format")
-    fun format(value: Float): String {
-        return ((value * 1000).toInt() / 1000.0).toString()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ILogger.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ILogger.kt
deleted file mode 100644
index e436b33..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ILogger.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-interface ILogger {
-    fun <T> withTracing(name: String, predicate: () -> T): T
-    fun v(tag: String, msg: String)
-    fun d(tag: String, msg: String)
-    fun i(tag: String, msg: String)
-    fun w(tag: String, msg: String)
-    fun e(tag: String, msg: String, error: Throwable? = null)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/IScenario.kt b/libraries/flicker/src/com/android/server/wm/traces/common/IScenario.kt
deleted file mode 100644
index eefc3ab..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/IScenario.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.traces.common
-
-import com.android.server.wm.traces.common.service.PlatformConsts
-
-interface IScenario {
-    val description: String
-    val key: String
-    val isEmpty: Boolean
-
-    val startRotation: PlatformConsts.Rotation
-    val endRotation: PlatformConsts.Rotation
-    val navBarMode: PlatformConsts.NavBar
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt
deleted file mode 100644
index a8d276d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ITrace.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-interface ITrace<Entry : ITraceEntry> {
-    @JsName("entries") val entries: Array<Entry>
-
-    @JsName("slice") fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ITrace<Entry>
-
-    /**
-     * @return an entry that matches exactly [timestamp]
-     * @throws if there is no entry in the trace at [timestamp]
-     */
-    @JsName("getEntryExactlyAt")
-    fun getEntryExactlyAt(timestamp: Timestamp): Entry {
-        return entries.firstOrNull { it.timestamp == timestamp }
-            ?: throw RuntimeException("Entry does not exist for timestamp $timestamp")
-    }
-
-    /**
-     * @return the entry that is "active' at [timestamp]
-     * ```
-     *         (the entry at [timestamp] or the one before it if no entry exists at [timestamp])
-     * @throws if
-     * ```
-     * the provided [timestamp] is before all entries in the trace
-     */
-    @JsName("getEntryAt")
-    fun getEntryAt(timestamp: Timestamp): Entry {
-        return entries.dropLastWhile { it.timestamp > timestamp }.lastOrNull()
-            ?: error("No entry at or before timestamp $timestamp")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ITraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ITraceEntry.kt
deleted file mode 100644
index 582422d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ITraceEntry.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/** Common interface for Layer and WindowManager trace entries. */
-interface ITraceEntry {
-    /** @return timestamp of current entry */
-    @JsName("timestamp") val timestamp: Timestamp
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Insets.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Insets.kt
deleted file mode 100644
index 2f29315..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Insets.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for RectProto objects representing insets
- *
- * This class is used by flicker and Winscope
- */
-class Insets private constructor(left: Int = 0, top: Int = 0, right: Int = 0, bottom: Int = 0) :
-    Rect(left, top, right, bottom) {
-    override val isEmpty: Boolean
-        get() = left == 0 && top == 0 && right == 0 && bottom == 0
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Insets
-            get() = withCache { Insets() }
-
-        @JsName("from")
-        fun from(left: Int, top: Int, right: Int, bottom: Int): Insets = withCache {
-            Insets(left, top, right, bottom)
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Insets) return false
-        if (!super.equals(other)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + isEmpty.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/LoggerBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/LoggerBuilder.kt
deleted file mode 100644
index 28c538c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/LoggerBuilder.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-class LoggerBuilder {
-    private var onV: (tag: String, msg: String) -> Unit = { tag, msg -> println("(V) $tag $msg") }
-    private var onD: (tag: String, msg: String) -> Unit = { tag, msg -> println("(D) $tag $msg") }
-    private var onI: (tag: String, msg: String) -> Unit = { tag, msg -> println("(I) $tag $msg") }
-    private var onW: (tag: String, msg: String) -> Unit = { tag, msg -> println("(W) $tag $msg") }
-    private var onE: (tag: String, msg: String, error: Throwable?) -> Unit = { tag, msg, error ->
-        println("(e) $tag $msg $error")
-        error?.printStackTrace()
-    }
-    private var onTracing: (name: String, predicate: () -> Any) -> Any = { name, predicate ->
-        try {
-            println("(withTracing#start) $name")
-            predicate()
-        } finally {
-            println("(withTracing#end) $name")
-        }
-    }
-
-    fun setV(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
-        onV = predicate
-    }
-
-    fun setD(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
-        onD = predicate
-    }
-
-    fun setI(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
-        onI = predicate
-    }
-
-    fun setW(predicate: (tag: String, msg: String) -> Unit): LoggerBuilder = apply {
-        onW = predicate
-    }
-
-    fun setE(predicate: (tag: String, msg: String, error: Throwable?) -> Unit): LoggerBuilder =
-        apply {
-            onE = predicate
-        }
-
-    fun setOnTracing(predicate: (name: String, predicate: () -> Any) -> Any): LoggerBuilder =
-        apply {
-            onTracing = predicate
-        }
-
-    fun build(): ILogger {
-        return object : ILogger {
-            override fun d(tag: String, msg: String) = onD(tag, msg)
-
-            override fun e(tag: String, msg: String, error: Throwable?) = onE(tag, msg, error)
-
-            override fun i(tag: String, msg: String) = onI(tag, msg)
-
-            override fun v(tag: String, msg: String) = onV(tag, msg)
-
-            override fun w(tag: String, msg: String) = onW(tag, msg)
-
-            override fun <T> withTracing(name: String, predicate: () -> T): T {
-                return onTracing(name, predicate as () -> Any) as T
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Matrix22.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Matrix22.kt
deleted file mode 100644
index d05b7eb..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Matrix22.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Representation of a matrix 3x3 used for layer transforms
- * ```
- *          |dsdx dsdy  tx|
- * ```
- * matrix = |dtdx dtdy ty|
- * ```
- *          |0    0     1 |
- * ```
- */
-open class Matrix22(
-    @JsName("dsdx") val dsdx: Float,
-    @JsName("dtdx") val dtdx: Float,
-    @JsName("dsdy") val dsdy: Float,
-    @JsName("dtdy") val dtdy: Float
-) {
-    @JsName("prettyPrint")
-    open fun prettyPrint(): String {
-        val dsdx = FloatFormatter.format(dsdx)
-        val dtdx = FloatFormatter.format(dtdx)
-        val dsdy = FloatFormatter.format(dsdy)
-        val dtdy = FloatFormatter.format(dtdy)
-        return "dsdx:$dsdx   dtdx:$dtdx   dsdy:$dsdy   dtdy:$dtdy"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Matrix22) return false
-
-        if (dsdx != other.dsdx) return false
-        if (dtdx != other.dtdx) return false
-        if (dsdy != other.dsdy) return false
-        if (dtdy != other.dtdy) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = dsdx.hashCode()
-        result = 31 * result + dtdx.hashCode()
-        result = 31 * result + dsdy.hashCode()
-        result = 31 * result + dtdy.hashCode()
-        return result
-    }
-
-    override fun toString(): String = prettyPrint()
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Matrix22
-            get() = withCache { Matrix22(0f, 0f, 0f, 0f) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Matrix33.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Matrix33.kt
deleted file mode 100644
index 1a4c328..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Matrix33.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Representation of a matrix 3x3 used for layer transforms
- * ```
- *          |dsdx dsdy  tx|
- * ```
- * matrix = |dtdx dtdy ty|
- * ```
- *          |0    0     1 |
- * ```
- */
-class Matrix33
-private constructor(
-    dsdx: Float = 0F,
-    dtdx: Float = 0F,
-    @JsName("tx") val tx: Float = 0F,
-    dsdy: Float = 0F,
-    dtdy: Float = 0F,
-    @JsName("ty") val ty: Float = 0F
-) : Matrix22(dsdx, dtdx, dsdy, dtdy) {
-    override fun prettyPrint(): String {
-        val parentPrint = super.prettyPrint()
-        val tx = FloatFormatter.format(dsdx)
-        val ty = FloatFormatter.format(dtdx)
-        return "$parentPrint   tx:$tx   ty:$ty"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Matrix33) return false
-        if (!super.equals(other)) return false
-
-        if (tx != other.tx) return false
-        if (ty != other.ty) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + tx.hashCode()
-        result = 31 * result + ty.hashCode()
-        return result
-    }
-
-    companion object {
-        val EMPTY: Matrix33
-            get() = withCache { from(dsdx = 0f, dtdx = 0f, tx = 0f, dsdy = 0f, dtdy = 0f, ty = 0f) }
-
-        @JsName("identity")
-        fun identity(x: Float, y: Float): Matrix33 = withCache {
-            from(dsdx = 1f, dtdx = 0f, x, dsdy = 0f, dtdy = 1f, y)
-        }
-
-        @JsName("rot270")
-        fun rot270(x: Float, y: Float): Matrix33 = withCache {
-            from(dsdx = 0f, dtdx = -1f, x, dsdy = 1f, dtdy = 0f, y)
-        }
-
-        @JsName("rot180")
-        fun rot180(x: Float, y: Float): Matrix33 = withCache {
-            from(dsdx = -1f, dtdx = 0f, x, dsdy = 0f, dtdy = -1f, y)
-        }
-
-        @JsName("rot90")
-        fun rot90(x: Float, y: Float): Matrix33 = withCache {
-            from(dsdx = 0f, dtdx = 1f, x, dsdy = -1f, dtdy = 0f, y)
-        }
-
-        @JsName("from")
-        fun from(
-            dsdx: Float,
-            dtdx: Float,
-            tx: Float,
-            dsdy: Float,
-            dtdy: Float,
-            ty: Float
-        ): Matrix33 = withCache { Matrix33(dsdx, dtdx, tx, dsdy, dtdy, ty) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/NullableDeviceStateDump.kt b/libraries/flicker/src/com/android/server/wm/traces/common/NullableDeviceStateDump.kt
deleted file mode 100644
index 67c913e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/NullableDeviceStateDump.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.traces.common
-
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import kotlin.js.JsName
-
-/**
- * Represents a state dump optionally containing the [WindowManagerState] and the
- * [BaseLayerTraceEntry] parsed.
- */
-open class NullableDeviceStateDump(
-    /** Parsed [WindowManagerState] */
-    @JsName("wmState") open val wmState: WindowManagerState?,
-
-    /** Parsed [BaseLayerTraceEntry] */
-    @JsName("layerState") open val layerState: BaseLayerTraceEntry?
-)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt
deleted file mode 100644
index 07667a8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Point.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for PointProto (frameworks/base/core/proto/android/graphics/point.proto)
- *
- * This class is used by flicker and Winscope
- */
-class Point private constructor(val x: Int = 0, val y: Int = 0) {
-    @JsName("prettyPrint") fun prettyPrint(): String = "($x, $y)"
-
-    override fun toString(): String = prettyPrint()
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Point) return false
-
-        if (x != other.x) return false
-        if (y != other.y) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = x
-        result = 31 * result + y
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Point
-            get() = withCache { Point() }
-
-        @JsName("from") fun from(x: Int, y: Int): Point = withCache { Point(x, y) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/PointF.kt b/libraries/flicker/src/com/android/server/wm/traces/common/PointF.kt
deleted file mode 100644
index 5a63fb8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/PointF.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for PositionProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
- *
- * This class is used by flicker and Winscope
- */
-class PointF private constructor(val x: Float = 0f, val y: Float = 0f) {
-    @JsName("prettyPrint")
-    fun prettyPrint(): String = "(${FloatFormatter.format(x)}, ${FloatFormatter.format(y)})"
-
-    override fun toString(): String = prettyPrint()
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is PointF) return false
-
-        if (x != other.x) return false
-        if (y != other.y) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = x.hashCode()
-        result = 31 * result + y.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: PointF
-            get() = withCache { PointF() }
-
-        @JsName("from") fun from(x: Float, y: Float): PointF = withCache { PointF(x, y) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Rect.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Rect.kt
deleted file mode 100644
index cd69f10..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Rect.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for RectProto
- * ```
- *     - frameworks/native/services/surfaceflinger/layerproto/common.proto and
- *     - frameworks/base/core/proto/android/graphics/rect.proto
- * ```
- * This class is used by flicker and Winscope
- */
-open class Rect
-internal constructor(
-    @JsName("left") val left: Int = 0,
-    @JsName("top") val top: Int = 0,
-    @JsName("right") val right: Int = 0,
-    @JsName("bottom") val bottom: Int = 0
-) {
-    @JsName("height")
-    val height: Int
-        get() = bottom - top
-    @JsName("width")
-    val width: Int
-        get() = right - left
-    @JsName("centerX") fun centerX(): Int = (left + right) / 2
-    @JsName("centerY") fun centerY(): Int = (top + bottom) / 2
-    /** Returns true if the rectangle is empty (left >= right or top >= bottom) */
-    @JsName("isEmpty")
-    open val isEmpty: Boolean
-        get() = width <= 0 || height <= 0
-
-    @JsName("isNotEmpty")
-    val isNotEmpty: Boolean
-        get() = !isEmpty
-
-    /** Returns a [RectF] version fo this rectangle. */
-    @JsName("toRectF")
-    fun toRectF(): RectF {
-        return RectF.from(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
-    }
-
-    @JsName("prettyPrint")
-    fun prettyPrint(): String = if (isEmpty) "[empty]" else "($left, $top) - ($right, $bottom)"
-
-    /**
-     * Returns true iff the specified rectangle r is inside or equal to this rectangle. An empty
-     * rectangle never contains another rectangle.
-     *
-     * @param rect The rectangle being tested for containment.
-     * @return true iff the specified rectangle r is inside or equal to this
-     * ```
-     *              rectangle
-     * ```
-     */
-    operator fun contains(rect: Rect): Boolean {
-        val thisRect = toRectF()
-        val otherRect = rect.toRectF()
-        return thisRect.contains(otherRect)
-    }
-
-    /**
-     * Returns a [Rect] where the dimensions don't exceed those of [crop]
-     *
-     * @param crop The crop that should be applied to this layer
-     */
-    @JsName("crop")
-    fun crop(crop: Rect): Rect {
-        val newLeft = maxOf(left, crop.left)
-        val newTop = maxOf(top, crop.top)
-        val newRight = minOf(right, crop.right)
-        val newBottom = minOf(bottom, crop.bottom)
-        return from(newLeft, newTop, newRight, newBottom)
-    }
-
-    /**
-     * Returns true if: fLeft <= x < fRight && fTop <= y < fBottom. Returns false if SkIRect is
-     * empty.
-     *
-     * Considers input to describe constructed SkIRect: (x, y, x + 1, y + 1) and returns true if
-     * constructed area is completely enclosed by SkIRect area.
-     *
-     * @param x test SkIPoint x-coordinate @param y test SkIPoint y-coordinate @return true if (x,
-     * y) is inside SkIRect
-     */
-    fun contains(x: Int, y: Int): Boolean {
-        return x in left until right && y in top until bottom
-    }
-
-    /**
-     * If the specified rectangle intersects this rectangle, return true and set this rectangle to
-     * that intersection, otherwise return false and do not change this rectangle. No check is
-     * performed to see if either rectangle is empty. To just test for intersection, use
-     * intersects()
-     *
-     * @param rect The rectangle being intersected with this rectangle.
-     * @return A rectangle with the intersection coordinates
-     */
-    @JsName("intersection")
-    fun intersection(rect: Rect): Rect {
-        val thisRect = toRectF()
-        val otherRect = rect.toRectF()
-        return thisRect.intersection(otherRect).toRect()
-    }
-
-    override fun hashCode(): Int {
-        var result = left
-        result = 31 * result + top
-        result = 31 * result + right
-        result = 31 * result + bottom
-        return result
-    }
-
-    override fun toString(): String = prettyPrint()
-
-    @JsName("clone")
-    fun clone(): Rect {
-        return from(left, top, right, bottom)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Rect) return false
-
-        if (left != other.left) return false
-        if (top != other.top) return false
-        if (right != other.right) return false
-        if (bottom != other.bottom) return false
-
-        return true
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Rect
-            get() = withCache { Rect() }
-
-        @JsName("from")
-        fun from(left: Int, top: Int, right: Int, bottom: Int): Rect = withCache {
-            Rect(left, top, right, bottom)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt b/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt
deleted file mode 100644
index aa76064..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/RectF.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for FloatRectProto (frameworks/native/services/surfaceflinger/layerproto/layers.proto)
- *
- * This class is used by flicker and Winscope
- */
-class RectF
-private constructor(
-    @JsName("left") val left: Float = 0f,
-    @JsName("top") val top: Float = 0f,
-    @JsName("right") val right: Float = 0f,
-    @JsName("bottom") val bottom: Float = 0f
-) {
-    @JsName("height")
-    val height: Float
-        get() = bottom - top
-    @JsName("width")
-    val width: Float
-        get() = right - left
-
-    /** Returns true if the rectangle is empty (left >= right or top >= bottom) */
-    @JsName("isEmpty")
-    val isEmpty: Boolean
-        get() = width <= 0f || height <= 0f
-    @JsName("isNotEmpty")
-    val isNotEmpty: Boolean
-        get() = !isEmpty
-
-    /**
-     * Returns a [Rect] version fo this rectangle.
-     *
-     * All fractional parts are rounded to 0
-     */
-    @JsName("toRect")
-    fun toRect(): Rect {
-        return Rect.from(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
-    }
-
-    /**
-     * Returns true iff the specified rectangle r is inside or equal to this rectangle. An empty
-     * rectangle never contains another rectangle.
-     *
-     * @param r The rectangle being tested for containment.
-     * @return true iff the specified rectangle r is inside or equal to this
-     * ```
-     *              rectangle
-     * ```
-     */
-    operator fun contains(r: RectF): Boolean {
-        // check for empty first
-        return this.left < this.right &&
-            this.top < this.bottom && // now check for containment
-            left <= r.left &&
-            top <= r.top &&
-            right >= r.right &&
-            bottom >= r.bottom
-    }
-
-    /**
-     * Returns a [RectF] where the dimensions don't exceed those of [crop]
-     *
-     * @param crop The crop that should be applied to this layer
-     */
-    @JsName("crop")
-    fun crop(crop: RectF): RectF {
-        val newLeft = maxOf(left, crop.left)
-        val newTop = maxOf(top, crop.top)
-        val newRight = minOf(right, crop.right)
-        val newBottom = minOf(bottom, crop.bottom)
-        return from(newLeft, newTop, newRight, newBottom)
-    }
-
-    /**
-     * If the rectangle specified by left,top,right,bottom intersects this rectangle, return true
-     * and set this rectangle to that intersection, otherwise return false and do not change this
-     * rectangle. No check is performed to see if either rectangle is empty. Note: To just test for
-     * intersection, use intersects()
-     *
-     * @param left The left side of the rectangle being intersected with this rectangle
-     * @param top The top of the rectangle being intersected with this rectangle
-     * @param right The right side of the rectangle being intersected with this rectangle.
-     * @param bottom The bottom of the rectangle being intersected with this rectangle.
-     * @return A rectangle with the intersection coordinates
-     */
-    @JsName("intersection")
-    fun intersection(left: Float, top: Float, right: Float, bottom: Float): RectF {
-        if (this.left < right && left < this.right && this.top <= bottom && top <= this.bottom) {
-            var intersectionLeft = this.left
-            var intersectionTop = this.top
-            var intersectionRight = this.right
-            var intersectionBottom = this.bottom
-
-            if (this.left < left) {
-                intersectionLeft = left
-            }
-            if (this.top < top) {
-                intersectionTop = top
-            }
-            if (this.right > right) {
-                intersectionRight = right
-            }
-            if (this.bottom > bottom) {
-                intersectionBottom = bottom
-            }
-            return from(intersectionLeft, intersectionTop, intersectionRight, intersectionBottom)
-        }
-        return EMPTY
-    }
-
-    /**
-     * If the specified rectangle intersects this rectangle, return true and set this rectangle to
-     * that intersection, otherwise return false and do not change this rectangle. No check is
-     * performed to see if either rectangle is empty. To just test for intersection, use
-     * intersects()
-     *
-     * @param r The rectangle being intersected with this rectangle.
-     * @return A rectangle with the intersection coordinates
-     */
-    @JsName("intersectionWithRect")
-    fun intersection(r: RectF): RectF = intersection(r.left, r.top, r.right, r.bottom)
-
-    @JsName("prettyPrint")
-    fun prettyPrint(): String =
-        if (isEmpty) {
-            "[empty]"
-        } else {
-            val left = FloatFormatter.format(left)
-            val top = FloatFormatter.format(top)
-            val right = FloatFormatter.format(right)
-            val bottom = FloatFormatter.format(bottom)
-            "($left, $top) - ($right, $bottom)"
-        }
-
-    override fun toString(): String = prettyPrint()
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is RectF) return false
-
-        if (left != other.left) return false
-        if (top != other.top) return false
-        if (right != other.right) return false
-        if (bottom != other.bottom) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = left.hashCode()
-        result = 31 * result + top.hashCode()
-        result = 31 * result + right.hashCode()
-        result = 31 * result + bottom.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: RectF
-            get() = withCache { RectF() }
-
-        @JsName("from")
-        fun from(left: Float, top: Float, right: Float, bottom: Float): RectF = withCache {
-            RectF(left, top, right, bottom)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Scenario.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Scenario.kt
deleted file mode 100644
index ba2d4fa..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Scenario.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-import com.android.server.wm.traces.common.service.PlatformConsts
-
-/**
- * Legacy flicker test scenario
- *
- * @param testClass
- * @param startRotation Initial screen rotation
- * @param endRotation Final screen rotation
- * @param navBarMode Navigation mode, such as 3 button or gestural.
- * @param _extraConfig Additional configurations
- *
- * Defaults to [startRotation]
- */
-class Scenario
-internal constructor(
-    val testClass: String,
-    override val startRotation: PlatformConsts.Rotation,
-    override val endRotation: PlatformConsts.Rotation,
-    override val navBarMode: PlatformConsts.NavBar,
-    _extraConfig: Map<String, Any?>,
-    override val description: String
-) : IScenario {
-    internal val extraConfig = _extraConfig.toMutableMap()
-
-    override val isEmpty = testClass.isEmpty()
-
-    override val key = if (isEmpty) "empty" else "${testClass}_$description"
-
-    /** If the initial screen rotation is 90 (landscape) or 180 (seascape) degrees */
-    val isLandscapeOrSeascapeAtStart: Boolean =
-        startRotation == PlatformConsts.Rotation.ROTATION_90 ||
-            startRotation == PlatformConsts.Rotation.ROTATION_270
-
-    val isGesturalNavigation = navBarMode == PlatformConsts.NavBar.MODE_GESTURAL
-
-    val isTablet: Boolean
-        get() =
-            extraConfig[IS_TABLET] as Boolean?
-                ?: error("$IS_TABLET property not initialized. Use [setIsTablet] to initialize ")
-
-    fun setIsTablet(isTablet: Boolean) {
-        extraConfig[IS_TABLET] = isTablet
-    }
-
-    fun <T> getConfigValue(key: String): T? = extraConfig[key] as T?
-
-    override fun toString(): String = key
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Scenario) return false
-
-        if (testClass != other.testClass) return false
-        if (startRotation != other.startRotation) return false
-        if (endRotation != other.endRotation) return false
-        if (navBarMode != other.navBarMode) return false
-        if (description != other.description) return false
-        if (extraConfig != other.extraConfig) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = testClass.hashCode()
-        result = 31 * result + startRotation.hashCode()
-        result = 31 * result + endRotation.hashCode()
-        result = 31 * result + navBarMode.hashCode()
-        result = 31 * result + description.hashCode()
-        result = 31 * result + extraConfig.hashCode()
-        return result
-    }
-
-    companion object {
-        internal const val IS_TABLET = "isTablet"
-        const val FAAS_BLOCKING = "faas:blocking"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/ScenarioBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/ScenarioBuilder.kt
deleted file mode 100644
index d87d370..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/ScenarioBuilder.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-import com.android.server.wm.traces.common.service.PlatformConsts
-
-/** Helper class to create [Scenario]s */
-class ScenarioBuilder {
-    private var testClass: String = ""
-    private var startRotation = DEFAULT_ROTATION
-    private var endRotation = DEFAULT_ROTATION
-    private var navBarMode = DEFAULT_NAVBAR_MODE
-    private var extraConfig = mutableMapOf<String, Any?>()
-    private var description = ""
-
-    fun fromScenario(other: Scenario) =
-        forClass(other.testClass)
-            .withStartRotation(other.startRotation)
-            .withEndRotation(other.endRotation)
-            .withNavBarMode(other.navBarMode)
-            .withExtraConfigs(other.extraConfig)
-            .withDescriptionOverride(other.description)
-
-    fun forClass(cls: String) = apply { testClass = cls }
-
-    fun withStartRotation(rotation: PlatformConsts.Rotation) = apply { startRotation = rotation }
-
-    fun withEndRotation(rotation: PlatformConsts.Rotation) = apply { endRotation = rotation }
-
-    fun withNavBarMode(mode: PlatformConsts.NavBar) = apply { navBarMode = mode }
-
-    fun withExtraConfig(key: String, value: Any?) = apply { extraConfig[key] = value }
-
-    fun withExtraConfigs(cfg: Map<String, Any?>) = apply { extraConfig.putAll(cfg) }
-
-    fun withDescriptionOverride(description: String) = apply { this.description = description }
-
-    fun build(): Scenario {
-        require(testClass.isNotEmpty()) { "Test class missing" }
-        val description =
-            description.ifEmpty {
-                buildString {
-                    append(startRotation.description)
-                    if (endRotation != startRotation) {
-                        append("_${endRotation.description}")
-                    }
-                    append("_${navBarMode.description}")
-                }
-            }
-        return Scenario(testClass, startRotation, endRotation, navBarMode, extraConfig, description)
-    }
-
-    fun createEmptyScenario(): Scenario =
-        Scenario(
-            testClass = "",
-            startRotation = DEFAULT_ROTATION,
-            endRotation = DEFAULT_ROTATION,
-            navBarMode = DEFAULT_NAVBAR_MODE,
-            _extraConfig = emptyMap(),
-            description =
-                defaultDescription(DEFAULT_ROTATION, DEFAULT_ROTATION, DEFAULT_NAVBAR_MODE)
-        )
-
-    companion object {
-        const val FAAS_BLOCKING = "faas:blocking"
-        val DEFAULT_ROTATION = PlatformConsts.Rotation.ROTATION_0
-        val DEFAULT_NAVBAR_MODE = PlatformConsts.NavBar.MODE_GESTURAL
-
-        private fun defaultDescription(
-            startOrientation: PlatformConsts.Rotation,
-            endOrientation: PlatformConsts.Rotation,
-            navBarMode: PlatformConsts.NavBar
-        ): String = buildString {
-            append(startOrientation.description)
-            if (endOrientation != startOrientation) {
-                append("_${endOrientation.description}")
-            }
-            append("_${navBarMode.description}")
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Size.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Size.kt
deleted file mode 100644
index 821fd30..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Size.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * Wrapper for SizeProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
- *
- * This class is used by flicker and Winscope
- */
-open class Size
-protected constructor(@JsName("width") val width: Int = 0, @JsName("height") val height: Int = 0) {
-    @JsName("isEmpty")
-    val isEmpty: Boolean
-        get() = height == 0 || width == 0
-
-    @JsName("isNotEmpty")
-    val isNotEmpty: Boolean
-        get() = !isEmpty
-
-    open fun prettyPrint(): String = "$width x $height"
-
-    override fun toString(): String = if (isEmpty) "[empty]" else prettyPrint()
-
-    override fun equals(other: Any?): Boolean =
-        other is Size && other.height == height && other.width == width
-
-    override fun hashCode(): Int {
-        var result = width
-        result = 31 * result + height
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Size
-            get() = withCache { Size() }
-        @JsName("from") fun from(width: Int, height: Int): Size = withCache { Size(width, height) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Timestamp.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Timestamp.kt
deleted file mode 100644
index b7dea3f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Timestamp.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.traces.common
-
-import kotlin.math.max
-
-/**
- * Time interface with all available timestamp types
- *
- * @param elapsedNanos Nanoseconds since boot, including time spent in sleep.
- * @param systemUptimeNanos Nanoseconds since boot, not counting time spent in deep sleep
- * @param unixNanos Nanoseconds since Unix epoch
- */
-data class Timestamp
-internal constructor(
-    val elapsedNanos: Long = 0L,
-    val systemUptimeNanos: Long = 0L,
-    val unixNanos: Long = 0L,
-    private val realTimestampFormatter: (Long) -> String
-) : Comparable<Timestamp> {
-    val hasElapsedTimestamp = elapsedNanos != 0L
-    val hasSystemUptimeTimestamp = systemUptimeNanos != 0L
-    val hasUnixTimestamp = unixNanos != 0L
-    val isEmpty = !hasElapsedTimestamp && !hasSystemUptimeTimestamp && !hasUnixTimestamp
-    val hasAllTimestamps = hasUnixTimestamp && hasSystemUptimeTimestamp && hasElapsedTimestamp
-
-    fun unixNanosToLogFormat(): String {
-        val seconds = unixNanos / SECOND_AS_NANOSECONDS
-        val nanos = unixNanos % SECOND_AS_NANOSECONDS
-        return "$seconds.${nanos.toString().padStart(9, '0')}"
-    }
-
-    override fun toString(): String {
-        return when {
-            isEmpty -> "<NO TIMESTAMP>"
-            hasUnixTimestamp -> "${realTimestampFormatter(unixNanos)} (${unixNanos}ns)"
-            hasSystemUptimeTimestamp ->
-                "${formatElapsedTimestamp(systemUptimeNanos)} (${systemUptimeNanos}ns)"
-            hasElapsedTimestamp -> "${formatElapsedTimestamp(elapsedNanos)} (${elapsedNanos}ns)"
-            else -> error("Timestamp had no valid timestamps sets")
-        }
-    }
-
-    operator fun minus(nanos: Long): Timestamp {
-        val elapsedNanos = max(this.elapsedNanos - nanos, 0L)
-        val systemUptimeNanos = max(this.systemUptimeNanos - nanos, 0L)
-        val unixNanos = max(this.unixNanos - nanos, 0L)
-        return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
-    }
-
-    operator fun minus(timestamp: Timestamp): Timestamp {
-        val elapsedNanos =
-            if (this.hasElapsedTimestamp && timestamp.hasElapsedTimestamp) {
-                this.elapsedNanos - timestamp.elapsedNanos
-            } else {
-                0L
-            }
-        val systemUptimeNanos =
-            if (this.hasSystemUptimeTimestamp && timestamp.hasSystemUptimeTimestamp) {
-                this.systemUptimeNanos - timestamp.systemUptimeNanos
-            } else {
-                0L
-            }
-        val unixNanos =
-            if (this.hasUnixTimestamp && timestamp.hasUnixTimestamp) {
-                this.unixNanos - timestamp.unixNanos
-            } else {
-                0L
-            }
-        return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
-    }
-
-    enum class PreferredType {
-        ELAPSED,
-        SYSTEM_UPTIME,
-        UNIX,
-        ANY
-    }
-
-    // The preferred and most accurate time type to use when running Timestamp operations or
-    // comparisons
-    private val preferredType: PreferredType
-        get() =
-            when {
-                hasElapsedTimestamp && hasSystemUptimeTimestamp -> PreferredType.ANY
-                hasElapsedTimestamp -> PreferredType.ELAPSED
-                hasSystemUptimeTimestamp -> PreferredType.SYSTEM_UPTIME
-                hasUnixTimestamp -> PreferredType.UNIX
-                else -> error("No valid timestamp available")
-            }
-
-    override fun compareTo(other: Timestamp): Int {
-        var useType = PreferredType.ANY
-        if (other.preferredType == this.preferredType) {
-            useType = this.preferredType
-        } else if (this.preferredType == PreferredType.ANY) {
-            useType = other.preferredType
-        } else if (other.preferredType == PreferredType.ANY) {
-            useType = this.preferredType
-        }
-
-        return when (useType) {
-            PreferredType.ELAPSED -> this.elapsedNanos.compareTo(other.elapsedNanos)
-            PreferredType.SYSTEM_UPTIME -> this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
-            PreferredType.UNIX,
-            PreferredType.ANY -> {
-                when {
-                    // If preferred timestamps don't match then comparing UNIX timestamps is
-                    // probably most accurate
-                    this.hasUnixTimestamp && other.hasUnixTimestamp ->
-                        this.unixNanos.compareTo(other.unixNanos)
-                    // Assumes timestamps are collected from the same device
-                    this.hasElapsedTimestamp && other.hasElapsedTimestamp ->
-                        this.elapsedNanos.compareTo(other.elapsedNanos)
-                    this.hasSystemUptimeTimestamp && other.hasSystemUptimeTimestamp ->
-                        this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
-                    else -> error("Timestamps $this and $other are not comparable")
-                }
-            }
-        }
-    }
-
-    companion object {
-        fun formatElapsedTimestamp(timestampNs: Long): String {
-            var remainingNs = timestampNs
-            val prettyTimestamp = StringBuilder()
-
-            val timeUnitToNanoSeconds =
-                mapOf(
-                    "d" to DAY_AS_NANOSECONDS,
-                    "h" to HOUR_AS_NANOSECONDS,
-                    "m" to MINUTE_AS_NANOSECONDS,
-                    "s" to SECOND_AS_NANOSECONDS,
-                    "ms" to MILLISECOND_AS_NANOSECONDS,
-                    "ns" to 1,
-                )
-
-            for ((timeUnit, ns) in timeUnitToNanoSeconds) {
-                val convertedTime = remainingNs / ns
-                remainingNs %= ns
-                if (prettyTimestamp.isEmpty() && convertedTime == 0L) {
-                    // Trailing 0 unit
-                    continue
-                }
-                prettyTimestamp.append("$convertedTime$timeUnit")
-            }
-
-            if (prettyTimestamp.isEmpty()) {
-                return "0ns"
-            }
-
-            return prettyTimestamp.toString()
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/TimestampFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/TimestampFactory.kt
deleted file mode 100644
index 5961281..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/TimestampFactory.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-import kotlin.js.JsName
-
-class TimestampFactory(private val realTimestampFormatter: (Long) -> String = { it.toString() }) {
-    private val empty by lazy { Timestamp(0L, 0L, 0L, realTimestampFormatter) }
-    private val min by lazy { Timestamp(1, 1, 1, realTimestampFormatter) }
-    private val max by lazy {
-        Timestamp(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, realTimestampFormatter)
-    }
-
-    fun min(): Timestamp = min
-    fun max(): Timestamp = max
-    fun empty(): Timestamp = empty
-
-    @JsName("fromLong")
-    fun from(
-        elapsedNanos: Long? = null,
-        systemUptimeNanos: Long? = null,
-        unixNanos: Long? = null,
-    ): Timestamp {
-        return Timestamp(
-            elapsedNanos ?: 0L,
-            systemUptimeNanos ?: 0L,
-            unixNanos ?: 0L,
-            realTimestampFormatter
-        )
-    }
-
-    @JsName("fromString")
-    fun from(
-        elapsedNanos: String? = null,
-        systemUptimeNanos: String? = null,
-        unixNanos: String? = null,
-    ): Timestamp {
-        return from(
-            (elapsedNanos ?: "0").toLong(),
-            (systemUptimeNanos ?: "0").toLong(),
-            (unixNanos ?: "0").toLong()
-        )
-    }
-
-    @JsName("fromWithOffsetLong")
-    fun from(elapsedNanos: Long, elapsedOffsetNanos: Long): Timestamp {
-        return Timestamp(
-            elapsedNanos = elapsedNanos,
-            unixNanos = elapsedNanos + elapsedOffsetNanos,
-            realTimestampFormatter = realTimestampFormatter
-        )
-    }
-
-    @JsName("fromWithOffsetString")
-    fun from(elapsedNanos: String, elapsedOffsetNanos: String): Timestamp {
-        val elapsedNanosLong = elapsedNanos.toLong()
-        return from(
-            elapsedNanos = elapsedNanosLong,
-            unixNanos = elapsedNanosLong + elapsedOffsetNanos.toLong()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/Utils.kt b/libraries/flicker/src/com/android/server/wm/traces/common/Utils.kt
deleted file mode 100644
index c70678c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/Utils.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.traces.common
-
-object Utils {
-    fun getTimestampsInRange(
-        entries: List<Timestamp>,
-        from: Timestamp,
-        to: Timestamp,
-        addInitialEntry: Boolean
-    ): Set<Timestamp> {
-        require(from <= to) { "`from` must be smaller or equal to `to` but was $from and $to" }
-
-        return when {
-            entries.isEmpty() -> {
-                emptySet()
-            }
-            to < entries.first() -> {
-                // Slice before all entries
-                emptySet()
-            }
-            entries.last() < from -> {
-                // Slice after all entries
-                if (addInitialEntry) {
-                    // Keep the last entry as the start entry of the sliced trace
-                    setOf(entries.last())
-                } else {
-                    emptySet()
-                }
-            }
-            else -> {
-                // first entry <= to
-                // last entry >= from
-                // -----|--------|------
-                //      [   to     to
-                //  from    from ]
-
-                var first = entries.indexOfFirst { it >= from }
-                require(first >= 0) { "No match found for first index" }
-                val last = entries.lastIndex - entries.reversed().indexOfFirst { it <= to }
-                require(last >= 0) { "No match found for last index" }
-
-                if (addInitialEntry && first > 0 && entries[first] > from) {
-                    // Include previous state since from timestamp is in between the previous
-                    // one and first, and the previous state is the state we were still at a
-                    // timestamp from.
-                    first--
-                }
-
-                entries.slice(first..last).toSet()
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt
deleted file mode 100644
index f27b0ca..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/WaitCondition.kt
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common
-
-import kotlin.js.JsName
-
-/**
- * The utility class to wait a condition with customized options. The default retry policy is 5
- * times with interval 1 second.
- *
- * @param <T> The type of the object to validate.
- *
- * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
- * ```
- *     println("Success");
- * ```
- * } // Wait for customized result with customized validation. String result =
- * WaitForCondition.Builder(supplier = () -> "Result string")
- * ```
- *         .withCondition(str -> str.equals("Expected string"))
- *         .withRetryIntervalMs(500)
- *         .withRetryLimit(3)
- *         .onFailure(str -> println("Failed on " + str)))
- *         .build()
- *         .waitFor()
- * ```
- * </pre>
- *
- * @param condition If it returns true, that means the condition is satisfied.
- */
-class WaitCondition<T>
-private constructor(
-    @JsName("supplier") private val supplier: () -> T,
-    @JsName("condition") private val condition: Condition<T>,
-    @JsName("retryLimit") private val retryLimit: Int,
-    @JsName("onLog") private val onLog: ((String, Boolean) -> Unit)?,
-    @JsName("onFailure") private val onFailure: ((T) -> Any)?,
-    @JsName("onRetry") private val onRetry: ((T) -> Any)?,
-    @JsName("onSuccess") private val onSuccess: ((T) -> Any)?,
-    @JsName("onStart") private val onStart: ((String) -> Any)?,
-    @JsName("onEnd") private val onEnd: (() -> Any)?
-) {
-    /** @return `false` if the condition does not satisfy within the time limit. */
-    @JsName("waitFor")
-    fun waitFor(): Boolean {
-        onStart?.invoke("waitFor")
-        try {
-            return doWaitFor()
-        } finally {
-            onEnd?.invoke()
-        }
-    }
-
-    private fun doWaitFor(): Boolean {
-        onLog?.invoke("***Waiting for $condition", /* isError */ false)
-        var currState: T? = null
-        var success = false
-        for (i in 0..retryLimit) {
-            val result = doWaitForRetry(i)
-            success = result.first
-            currState = result.second
-            if (success) {
-                break
-            } else if (i < retryLimit) {
-                onRetry?.invoke(currState)
-            }
-        }
-
-        return if (success) {
-            true
-        } else {
-            doNotifyFailure(currState)
-            false
-        }
-    }
-
-    private fun doWaitForRetry(retryNr: Int): Pair<Boolean, T> {
-        onStart?.invoke("doWaitForRetry")
-        try {
-            val currState = supplier.invoke()
-            return if (condition.isSatisfied(currState)) {
-                onLog?.invoke("***Waiting for $condition ... Success!", /* isError */ false)
-                onSuccess?.invoke(currState)
-                Pair(true, currState)
-            } else {
-                val detailedMessage = condition.getMessage(currState)
-                onLog?.invoke(
-                    "***Waiting for $detailedMessage... retry=${retryNr + 1}",
-                    /* isError */ true
-                )
-                Pair(false, currState)
-            }
-        } finally {
-            onEnd?.invoke()
-        }
-    }
-
-    private fun doNotifyFailure(currState: T?) {
-        val detailedMessage =
-            if (currState != null) {
-                condition.getMessage(currState)
-            } else {
-                condition.toString()
-            }
-        onLog?.invoke("***Waiting for $detailedMessage ... Failed!", /* isError */ true)
-        if (onFailure != null) {
-            require(currState != null) { "Missing last result for failure notification" }
-            onFailure.invoke(currState)
-        }
-    }
-
-    class Builder<T>(
-        @JsName("supplier") private val supplier: () -> T,
-        @JsName("retryLimit") private var retryLimit: Int
-    ) {
-        @JsName("conditions") private val conditions = mutableListOf<Condition<T>>()
-        private var onStart: ((String) -> Any)? = null
-        private var onEnd: (() -> Any)? = null
-        private var onFailure: ((T) -> Any)? = null
-        private var onRetry: ((T) -> Any)? = null
-        private var onSuccess: ((T) -> Any)? = null
-        private var onLog: ((String, Boolean) -> Unit)? = null
-
-        @JsName("withCondition")
-        fun withCondition(condition: Condition<T>) = apply { conditions.add(condition) }
-
-        @JsName("withConditionAndMessage")
-        fun withCondition(message: String, condition: (T) -> Boolean) = apply {
-            withCondition(Condition(message, condition))
-        }
-
-        @JsName("spreadConditionList")
-        private fun spreadConditionList(): List<Condition<T>> =
-            conditions.flatMap {
-                if (it is ConditionList<T>) {
-                    it.conditions
-                } else {
-                    listOf(it)
-                }
-            }
-
-        /**
-         * Executes the action when the condition does not satisfy within the time limit. The passed
-         * object to the consumer will be the last result from the supplier.
-         */
-        @JsName("onFailure")
-        fun onFailure(onFailure: (T) -> Any): Builder<T> = apply { this.onFailure = onFailure }
-
-        @JsName("onLog")
-        fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> = apply { this.onLog = onLog }
-
-        @JsName("onRetry")
-        fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = apply { this.onRetry = onRetry }
-
-        @JsName("onStart")
-        fun onStart(onStart: ((String) -> Any)? = null): Builder<T> = apply {
-            this.onStart = onStart
-        }
-
-        @JsName("onEnd")
-        fun onEnd(onEnd: (() -> Any)? = null): Builder<T> = apply { this.onEnd = onEnd }
-
-        @JsName("onSuccess")
-        fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = apply {
-            this.onSuccess = onRetry
-        }
-
-        @JsName("build")
-        fun build(): WaitCondition<T> =
-            WaitCondition(
-                supplier,
-                ConditionList(spreadConditionList()),
-                retryLimit,
-                onLog,
-                onFailure,
-                onRetry,
-                onSuccess,
-                onStart,
-                onEnd
-            )
-    }
-
-    companion object {
-        // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
-        // constant time, currently keep the default as 5*1s because most of the original code
-        // uses it, and some tests might be sensitive to the waiting interval.
-        @JsName("DEFAULT_RETRY_LIMIT") const val DEFAULT_RETRY_LIMIT = 50
-        @JsName("DEFAULT_RETRY_INTERVAL_MS") const val DEFAULT_RETRY_INTERVAL_MS = 100L
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt
deleted file mode 100644
index 88be319..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/WindowManagerConditionsFactory.kt
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * 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.traces.common
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.layers.Transform
-import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagSet
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import kotlin.js.JsName
-
-object WindowManagerConditionsFactory {
-    private fun getNavBarComponent(wmState: WindowManagerState) =
-        if (wmState.isTablet) ComponentNameMatcher.TASK_BAR else ComponentNameMatcher.NAV_BAR
-
-    /**
-     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
-     * windows are visible
-     */
-    @JsName("isNavOrTaskBarVisible")
-    fun isNavOrTaskBarVisible(): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(
-                isNavOrTaskBarWindowVisible(),
-                isNavOrTaskBarLayerVisible(),
-                isNavOrTaskBarLayerOpaque()
-            )
-        )
-
-    /**
-     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
-     * windows are visible
-     */
-    @JsName("isNavOrTaskBarWindowVisible")
-    fun isNavOrTaskBarWindowVisible(): Condition<DeviceStateDump> =
-        Condition("isNavBarOrTaskBarWindowVisible") {
-            val component = getNavBarComponent(it.wmState)
-            it.wmState.isWindowSurfaceShown(component)
-        }
-
-    /**
-     * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
-     * layers are visible
-     */
-    @JsName("isNavOrTaskBarLayerVisible")
-    fun isNavOrTaskBarLayerVisible(): Condition<DeviceStateDump> =
-        Condition("isNavBarOrTaskBarLayerVisible") {
-            val component = getNavBarComponent(it.wmState)
-            it.layerState.isVisible(component)
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
-    @JsName("isNavOrTaskBarLayerOpaque")
-    fun isNavOrTaskBarLayerOpaque(): Condition<DeviceStateDump> =
-        Condition("isNavOrTaskBarLayerOpaque") {
-            val component = getNavBarComponent(it.wmState)
-            it.layerState.getLayerWithBuffer(component)?.color?.isOpaque ?: false
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
-    @JsName("isNavBarVisible")
-    fun isNavBarVisible(): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(isNavBarWindowVisible(), isNavBarLayerVisible(), isNavBarLayerOpaque())
-        )
-
-    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
-    @JsName("isNavBarWindowVisible")
-    fun isNavBarWindowVisible(): Condition<DeviceStateDump> =
-        Condition("isNavBarWindowVisible") {
-            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.NAV_BAR)
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is visible */
-    @JsName("isNavBarLayerVisible")
-    fun isNavBarLayerVisible(): Condition<DeviceStateDump> =
-        isLayerVisible(ComponentNameMatcher.NAV_BAR)
-
-    /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
-    @JsName("isNavBarLayerOpaque")
-    fun isNavBarLayerOpaque(): Condition<DeviceStateDump> =
-        Condition("isNavBarLayerOpaque") {
-            it.layerState.getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)?.color?.isOpaque ?: false
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
-    @JsName("isTaskBarVisible")
-    fun isTaskBarVisible(): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(isTaskBarWindowVisible(), isTaskBarLayerVisible(), isTaskBarLayerOpaque())
-        )
-
-    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
-    @JsName("isTaskBarWindowVisible")
-    fun isTaskBarWindowVisible(): Condition<DeviceStateDump> =
-        Condition("isTaskBarWindowVisible") {
-            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.TASK_BAR)
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is visible */
-    @JsName("isTaskBarLayerVisible")
-    fun isTaskBarLayerVisible(): Condition<DeviceStateDump> =
-        isLayerVisible(ComponentNameMatcher.TASK_BAR)
-
-    /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is opaque */
-    @JsName("isTaskBarLayerOpaque")
-    fun isTaskBarLayerOpaque(): Condition<DeviceStateDump> =
-        Condition("isTaskBarLayerOpaque") {
-            it.layerState.getLayerWithBuffer(ComponentNameMatcher.TASK_BAR)?.color?.isOpaque
-                ?: false
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
-    @JsName("isStatusBarVisible")
-    fun isStatusBarVisible(): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(isStatusBarWindowVisible(), isStatusBarLayerVisible(), isStatusBarLayerOpaque())
-        )
-
-    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
-    @JsName("isStatusBarWindowVisible")
-    fun isStatusBarWindowVisible(): Condition<DeviceStateDump> =
-        Condition("isStatusBarWindowVisible") {
-            it.wmState.isWindowSurfaceShown(ComponentNameMatcher.STATUS_BAR)
-        }
-
-    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is visible */
-    @JsName("isStatusBarLayerVisible")
-    fun isStatusBarLayerVisible(): Condition<DeviceStateDump> =
-        isLayerVisible(ComponentNameMatcher.STATUS_BAR)
-
-    /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is opaque */
-    @JsName("isStatusBarLayerOpaque")
-    fun isStatusBarLayerOpaque(): Condition<DeviceStateDump> =
-        Condition("isStatusBarLayerOpaque") {
-            it.layerState.getLayerWithBuffer(ComponentNameMatcher.STATUS_BAR)?.color?.isOpaque
-                ?: false
-        }
-
-    @JsName("isHomeActivityVisible")
-    fun isHomeActivityVisible(): Condition<DeviceStateDump> =
-        Condition("isHomeActivityVisible") { it.wmState.isHomeActivityVisible }
-
-    @JsName("isRecentsActivityVisible")
-    fun isRecentsActivityVisible(): Condition<DeviceStateDump> =
-        Condition("isRecentsActivityVisible") {
-            it.wmState.isHomeActivityVisible || it.wmState.isRecentsActivityVisible
-        }
-
-    @JsName("isLauncherLayerVisible")
-    fun isLauncherLayerVisible(): Condition<DeviceStateDump> =
-        Condition("isLauncherLayerVisible") {
-            it.layerState.isVisible(ComponentNameMatcher.LAUNCHER) ||
-                it.layerState.isVisible(ComponentNameMatcher.AOSP_LAUNCHER)
-        }
-
-    /**
-     * Condition to check if WM app transition is idle
-     *
-     * Because in shell transitions, active recents animation is running transition (never idle)
-     * this method always assumed recents are idle
-     */
-    @JsName("isAppTransitionIdle")
-    fun isAppTransitionIdle(displayId: Int): Condition<DeviceStateDump> =
-        Condition("isAppTransitionIdle[$displayId]") {
-            (it.wmState.isHomeRecentsComponent && it.wmState.isHomeActivityVisible) ||
-                it.wmState.isRecentsActivityVisible ||
-                it.wmState.getDisplay(displayId)?.appTransitionState ==
-                    WindowManagerState.APP_STATE_IDLE
-        }
-
-    @JsName("containsActivity")
-    fun containsActivity(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("containsActivity[${componentMatcher.toActivityIdentifier()}]") {
-            it.wmState.containsActivity(componentMatcher)
-        }
-
-    @JsName("containsWindow")
-    fun containsWindow(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("containsWindow[${componentMatcher.toWindowIdentifier()}]") {
-            it.wmState.containsWindow(componentMatcher)
-        }
-
-    @JsName("isWindowSurfaceShown")
-    fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("isWindowSurfaceShown[${componentMatcher.toWindowIdentifier()}]") {
-            it.wmState.isWindowSurfaceShown(componentMatcher)
-        }
-
-    @JsName("isActivityVisible")
-    fun isActivityVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("isActivityVisible[${componentMatcher.toActivityIdentifier()}]") {
-            it.wmState.isActivityVisible(componentMatcher)
-        }
-
-    @JsName("isWMStateComplete")
-    fun isWMStateComplete(): Condition<DeviceStateDump> =
-        Condition("isWMStateComplete") { it.wmState.isComplete() }
-
-    @JsName("hasRotation")
-    fun hasRotation(
-        expectedRotation: PlatformConsts.Rotation,
-        displayId: Int
-    ): Condition<DeviceStateDump> {
-        val hasRotationCondition =
-            Condition<DeviceStateDump>("hasRotation[$expectedRotation, display=$displayId]") {
-                val currRotation = it.wmState.getRotation(displayId)
-                currRotation == expectedRotation
-            }
-        return ConditionList(
-            listOf(
-                hasRotationCondition,
-                isLayerVisible(ComponentNameMatcher.ROTATION).negate(),
-                isLayerVisible(ComponentNameMatcher.BACK_SURFACE).negate(),
-                hasLayersAnimating().negate()
-            )
-        )
-    }
-
-    @JsName("isWindowVisible")
-    fun isWindowVisible(
-        componentMatcher: IComponentMatcher,
-        displayId: Int = 0
-    ): Condition<DeviceStateDump> =
-        ConditionList(
-            containsActivity(componentMatcher),
-            containsWindow(componentMatcher),
-            isActivityVisible(componentMatcher),
-            isWindowSurfaceShown(componentMatcher),
-            isAppTransitionIdle(displayId)
-        )
-
-    @JsName("isLayerVisible")
-    fun isLayerVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("isLayerVisible[${componentMatcher.toLayerIdentifier()}]") {
-            it.layerState.isVisible(componentMatcher)
-        }
-
-    @JsName("isLayerVisibleForLayerId")
-    fun isLayerVisible(layerId: Int): Condition<DeviceStateDump> =
-        Condition("isLayerVisible[layerId=$layerId]") {
-            it.layerState.getLayerById(layerId)?.isVisible ?: false
-        }
-
-    @JsName("isLayerColorAlphaOne")
-    fun isLayerColorAlphaOne(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
-        Condition("isLayerColorAlphaOne[${componentMatcher.toLayerIdentifier()}]") {
-            it.layerState.visibleLayers
-                .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
-                .any { layer -> layer.color.isOpaque }
-        }
-
-    @JsName("isLayerColorAlphaOneForLayerId")
-    fun isLayerColorAlphaOne(layerId: Int): Condition<DeviceStateDump> =
-        Condition("isLayerColorAlphaOne[$layerId]") {
-            val layer = it.layerState.getLayerById(layerId)
-            layer?.color?.a == 1.0f
-        }
-
-    @JsName("isLayerTransformFlagSet")
-    fun isLayerTransformFlagSet(
-        componentMatcher: IComponentMatcher,
-        transform: Int
-    ): Condition<DeviceStateDump> =
-        Condition(
-            "isLayerTransformFlagSet[" +
-                "${componentMatcher.toLayerIdentifier()}," +
-                "transform=$transform]"
-        ) {
-            it.layerState.visibleLayers
-                .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
-                .any { layer -> isTransformFlagSet(layer, transform) }
-        }
-
-    @JsName("isLayerTransformFlagSetForLayerId")
-    fun isLayerTransformFlagSet(layerId: Int, transform: Int): Condition<DeviceStateDump> =
-        Condition("isLayerTransformFlagSet[$layerId, $transform]") {
-            val layer = it.layerState.getLayerById(layerId)
-            layer?.transform?.type?.isFlagSet(transform) ?: false
-        }
-
-    @JsName("isLayerTransformIdentity")
-    fun isLayerTransformIdentity(layerId: Int): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(
-                isLayerTransformFlagSet(layerId, Transform.SCALE_VAL).negate(),
-                isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL).negate(),
-                isLayerTransformFlagSet(layerId, Transform.ROTATE_VAL).negate()
-            )
-        )
-
-    @JsName("isTransformFlagSet")
-    private fun isTransformFlagSet(layer: Layer, transform: Int): Boolean =
-        layer.transform.type?.isFlagSet(transform) ?: false
-
-    @JsName("hasLayersAnimating")
-    fun hasLayersAnimating(): Condition<DeviceStateDump> {
-        var prevState: DeviceStateDump? = null
-        return ConditionList(
-            Condition("hasLayersAnimating") {
-                val result = it.layerState.isAnimating(prevState?.layerState)
-                prevState = it
-                result
-            },
-            isLayerVisible(ComponentNameMatcher.SNAPSHOT).negate(),
-            isLayerVisible(ComponentNameMatcher.SPLASH_SCREEN).negate()
-        )
-    }
-
-    @JsName("isPipWindowLayerSizeMatch")
-    fun isPipWindowLayerSizeMatch(layerId: Int): Condition<DeviceStateDump> =
-        Condition("isPipWindowLayerSizeMatch[layerId=$layerId]") {
-            val pipWindow =
-                it.wmState.pinnedWindows.firstOrNull { pinnedWindow ->
-                    pinnedWindow.layerId == layerId
-                }
-                    ?: error("Unable to find window with layerId $layerId")
-            val windowHeight = pipWindow.frame.height.toFloat()
-            val windowWidth = pipWindow.frame.width.toFloat()
-
-            val pipLayer = it.layerState.getLayerById(layerId)
-            val layerHeight =
-                pipLayer?.sourceBounds?.height ?: error("Unable to find layer with id $layerId")
-            val layerWidth = pipLayer.sourceBounds.width
-
-            windowHeight == layerHeight && windowWidth == layerWidth
-        }
-
-    @JsName("hasPipWindow")
-    fun hasPipWindow(): Condition<DeviceStateDump> =
-        Condition("hasPipWindow") { it.wmState.hasPipWindow() }
-
-    @JsName("isImeShown")
-    fun isImeShown(displayId: Int): Condition<DeviceStateDump> =
-        ConditionList(
-            listOf(
-                isImeOnDisplay(displayId),
-                isLayerVisible(ComponentNameMatcher.IME),
-                isImeSurfaceShown(),
-                isWindowSurfaceShown(ComponentNameMatcher.IME)
-            )
-        )
-
-    @JsName("isImeOnDisplay")
-    private fun isImeOnDisplay(displayId: Int): Condition<DeviceStateDump> =
-        Condition("isImeOnDisplay[$displayId]") {
-            it.wmState.inputMethodWindowState?.displayId == displayId
-        }
-
-    @JsName("isImeSurfaceShown")
-    private fun isImeSurfaceShown(): Condition<DeviceStateDump> =
-        Condition("isImeSurfaceShown") { it.wmState.inputMethodWindowState?.isSurfaceShown == true }
-
-    @JsName("isAppLaunchEnded")
-    fun isAppLaunchEnded(taskId: Int): Condition<DeviceStateDump> =
-        Condition("containsVisibleAppLaunchWindow[taskId=$taskId]") { dump ->
-            val windowStates =
-                dump.wmState.getRootTask(taskId)?.activities?.flatMap {
-                    it.children.filterIsInstance<WindowState>()
-                }
-            windowStates != null &&
-                windowStates.none {
-                    it.attributes.type == PlatformConsts.TYPE_APPLICATION_STARTING && it.isVisible
-                }
-        }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/WindowingMode.kt b/libraries/flicker/src/com/android/server/wm/traces/common/WindowingMode.kt
deleted file mode 100644
index 81a7997..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/WindowingMode.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common
-
-import kotlin.js.JsName
-
-enum class WindowingMode(val value: Int) {
-    /** Windowing mode is currently not defined. */
-    WINDOWING_MODE_UNDEFINED(0),
-
-    /** Occupies the full area of the screen or the parent container. */
-    WINDOWING_MODE_FULLSCREEN(1),
-
-    /** Always on-top (always visible). of other siblings in its parent container. */
-    WINDOWING_MODE_PINNED(2),
-
-    /** Can be freely resized within its parent container. */
-    WINDOWING_MODE_FREEFORM(5),
-
-    /** Generic multi-window with no presentation attribution from the window manager. */
-    WINDOWING_MODE_MULTI_WINDOW(6);
-
-    companion object {
-        @JsName("fromInt")
-        fun fromInt(value: Int) =
-            values().firstOrNull { it.value == value }
-                ?: error("No valid windowing mode for id $value")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionData.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionData.kt
deleted file mode 100644
index b8fcdfc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionData.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import kotlin.reflect.KClass
-
-/** Class containing basic data about an assertion */
-data class AssertionData(
-    /** Segment of the trace where the assertion will be applied (e.g., start, end). */
-    val tag: String,
-    /** Expected run result type */
-    val expectedSubjectClass: KClass<out FlickerSubject>,
-    /** Assertion command */
-    val assertion: FlickerSubject.() -> Unit
-) {
-    /**
-     * Extracts the data from the result and executes the assertion
-     *
-     * @param run Run to be asserted
-     */
-    fun checkAssertion(run: SubjectsParser) {
-        val subjects = run.getSubjects(tag).filter { expectedSubjectClass.isInstance(it) }
-        if (subjects.isEmpty()) {
-            return
-        }
-        subjects.forEach { it.run { assertion(this) } }
-    }
-
-    override fun toString(): String = buildString {
-        append("AssertionData(tag='")
-        append(tag)
-        append("', expectedSubjectClass='")
-        append(expectedSubjectClass.simpleName)
-        append("', assertion='")
-        append(assertion)
-        append(")")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionsChecker.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionsChecker.kt
deleted file mode 100644
index fcc00bc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/AssertionsChecker.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import kotlin.math.max
-
-/**
- * Runs sequences of assertions on sequences of subjects.
- *
- * Starting at the first assertion and first trace entry, executes the assertions iteratively on the
- * trace until all assertions and trace entries succeed.
- *
- * @param <T> trace entry type </T>
- */
-class AssertionsChecker<T : FlickerSubject> {
-    private val assertions = mutableListOf<CompoundAssertion<T>>()
-    private var skipUntilFirstAssertion = false
-
-    internal fun isEmpty() = assertions.isEmpty()
-
-    /** Add [assertion] to a new [CompoundAssertion] block. */
-    fun add(name: String, isOptional: Boolean = false, assertion: (T) -> Unit) {
-        assertions.add(CompoundAssertion(assertion, name, isOptional))
-    }
-
-    /** Append [assertion] to the last existing set of assertions. */
-    fun append(name: String, isOptional: Boolean = false, assertion: (T) -> Unit) {
-        assertions.last().add(assertion, name, isOptional)
-    }
-
-    /**
-     * Steps through each trace entry checking if provided assertions are true in the order they are
-     * added. Each assertion must be true for at least a single trace entry.
-     *
-     * This can be used to check for asserting a change in property over a trace. Such as visibility
-     * for a window changes from true to false or top-most window changes from A to B and back to A
-     * again.
-     *
-     * It is also possible to ignore failures on initial elements, until the first assertion passes,
-     * this allows the trace to be recorded for longer periods, and the checks to happen only after
-     * some time.
-     *
-     * @param entries list of entries to perform assertions on
-     * @return list of failed assertion results
-     */
-    fun test(entries: List<T>) {
-        if (assertions.isEmpty() || entries.isEmpty()) {
-            return
-        }
-
-        var entryIndex = 0
-        var assertionIndex = 0
-        var lastPassedAssertionIndex = -1
-        val assertionTrace = mutableListOf<String>()
-        while (assertionIndex < assertions.size && entryIndex < entries.size) {
-            val currentAssertion = assertions[assertionIndex]
-            val currEntry = entries[entryIndex]
-            try {
-                val log =
-                    "${assertionIndex + 1}/${assertions.size}:[${currentAssertion.name}]\t" +
-                        "Entry: ${entryIndex + 1}/${entries.size} $currEntry"
-                assertionTrace.add(log)
-                currentAssertion.invoke(currEntry)
-                lastPassedAssertionIndex = assertionIndex
-                entryIndex++
-            } catch (e: Throwable) {
-                // ignore errors at the start of the trace
-                val ignoreFailure = skipUntilFirstAssertion && lastPassedAssertionIndex == -1
-                if (ignoreFailure) {
-                    entryIndex++
-                    continue
-                }
-                // failure is an optional assertion, just consider it passed skip it
-                if (currentAssertion.isOptional) {
-                    lastPassedAssertionIndex = assertionIndex
-                    assertionIndex++
-                    continue
-                }
-                if (lastPassedAssertionIndex != assertionIndex) {
-                    val prevEntry = entries[max(entryIndex - 1, 0)]
-                    prevEntry.fail(e)
-                }
-                assertionIndex++
-                if (assertionIndex == assertions.size) {
-                    val prevEntry = entries[max(entryIndex - 1, 0)]
-                    prevEntry.fail(e)
-                }
-            }
-        }
-        // Didn't pass any assertions
-        if (lastPassedAssertionIndex == -1 && assertions.isNotEmpty()) {
-            entries.first().fail("Assertion never passed", assertions.first())
-        }
-
-        val untestedAssertions = assertions.drop(assertionIndex + 1)
-        if (untestedAssertions.any { !it.isOptional }) {
-            val passedAssertionsFacts = assertions.take(assertionIndex).map { Fact("Passed", it) }
-            val untestedAssertionsFacts = untestedAssertions.map { Fact("Untested", it) }
-            val trace = assertionTrace.map { Fact("Trace", it) }
-            val reason = mutableListOf<Fact>()
-            reason.addAll(passedAssertionsFacts)
-            reason.add(Fact("Assertion never failed", assertions[assertionIndex]))
-            reason.addAll(untestedAssertionsFacts)
-            reason.addAll(trace)
-            entries.first().fail(reason)
-        }
-    }
-
-    /**
-     * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
-     * end of the trace without passing any assertion, return a failure with the name/reason from
-     * the first assertion
-     */
-    fun skipUntilFirstAssertion() {
-        skipUntilFirstAssertion = true
-    }
-
-    fun isEqual(other: Any?): Boolean {
-        if (
-            other !is AssertionsChecker<*> ||
-                skipUntilFirstAssertion != other.skipUntilFirstAssertion
-        ) {
-            return false
-        }
-        assertions.forEachIndexed { index, assertion ->
-            if (assertion != other.assertions[index]) {
-                return false
-            }
-        }
-        return true
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/CompoundAssertion.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/CompoundAssertion.kt
deleted file mode 100644
index facdfbf..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/CompoundAssertion.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-/** Utility class to store assertions composed of multiple individual assertions */
-class CompoundAssertion<T>(assertion: (T) -> Unit, name: String, optional: Boolean) :
-    IAssertion<T> {
-    private val assertions = mutableListOf<NamedAssertion<T>>()
-
-    init {
-        add(assertion, name, optional)
-    }
-
-    override val isOptional
-        get() = assertions.all { it.isOptional }
-
-    override val name
-        get() = assertions.joinToString(" and ") { it.name }
-
-    /**
-     * Executes all [assertions] on [target]
-     *
-     * In case of failure, returns the first non-optional failure (if available) or the first failed
-     * assertion
-     */
-    override fun invoke(target: T) {
-        val failures =
-            assertions.mapNotNull { assertion ->
-                val error = kotlin.runCatching { assertion.invoke(target) }.exceptionOrNull()
-                if (error != null) {
-                    Pair(assertion, error)
-                } else {
-                    null
-                }
-            }
-        val nonOptionalFailure = failures.firstOrNull { !it.first.isOptional }
-        if (nonOptionalFailure != null) {
-            throw nonOptionalFailure.second
-        }
-        val firstFailure = failures.firstOrNull()
-        // Only throw first failure if all siblings are also optional otherwise don't throw anything
-        // If the CompoundAssertion is fully optional (i.e. all assertions in the compound assertion
-        // are optional), then we want to make sure the AssertionsChecker knows about the failure to
-        // not advance to the next state. Otherwise, the AssertionChecker doesn't need to know about
-        // the failure and can just consider the assertion as passed and advance to the next state
-        // since there were non-optional assertions which passed.
-        if (firstFailure != null && isOptional) {
-            throw firstFailure.second
-        }
-    }
-
-    /** Adds a new assertion to the list */
-    fun add(assertion: (T) -> Unit, name: String, optional: Boolean) {
-        assertions.add(NamedAssertion(assertion, name, optional))
-    }
-
-    override fun toString(): String = name
-
-    override fun equals(other: Any?): Boolean {
-        if (other !is CompoundAssertion<*>) {
-            return false
-        }
-        if (!super.equals(other)) {
-            return false
-        }
-        assertions.forEachIndexed { index, assertion ->
-            if (assertion != other.assertions[index]) {
-                return false
-            }
-        }
-        return true
-    }
-
-    override fun hashCode(): Int {
-        return assertions.hashCode()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/Fact.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/Fact.kt
deleted file mode 100644
index 3fec6b4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/Fact.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-/** A string key-value pair in a failure message, such as "expected: abc" or "but was: xyz." */
-data class Fact(val key: String, val value: String) {
-
-    constructor(key: String, value: Any? = null) : this(key, "$value")
-
-    override fun toString(): String {
-        return if (value.isEmpty()) key else "$key: $value"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/FlickerAssertionError.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/FlickerAssertionError.kt
deleted file mode 100644
index 3f5ea45..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/FlickerAssertionError.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-import kotlin.AssertionError
-
-/** Exception type for assertion errors caused by flicker subjects */
-class FlickerAssertionError(message: String, cause: Throwable?) : AssertionError(message, cause)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/IAssertion.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/IAssertion.kt
deleted file mode 100644
index f028571..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/IAssertion.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-/**
- * Checks assertion on a single trace entry.
- *
- * @param <T> trace entry type to perform the assertion on. </T>
- */
-interface IAssertion<T> {
-    val isOptional: Boolean
-    val name: String
-    operator fun invoke(target: T)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/NamedAssertion.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/NamedAssertion.kt
deleted file mode 100644
index 6b96981..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/NamedAssertion.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.assertions
-
-/**
- * Utility class to store assertions with an identifier to help generate more useful debug data when
- * dealing with multiple assertions.
- *
- * @param predicate Assertion to execute
- * @param name Assertion name
- * @param isOptional If the assertion is optional (can fail) or not (must pass)
- */
-open class NamedAssertion<T>(
-    val predicate: (T) -> Unit,
-    override val name: String,
-    override val isOptional: Boolean = false
-) : IAssertion<T> {
-    override operator fun invoke(target: T) = predicate(target)
-    override fun toString(): String = "Assertion($name)${if (isOptional) "[optional]" else ""}"
-
-    /**
-     * We can't check the actual assertion is the same. We are checking for the name, which should
-     * have a 1:1 correspondence with the assertion, but there is no actual guarantee of the same
-     * execution of the assertion even if isEqual() is true.
-     */
-    override fun equals(other: Any?): Boolean {
-        if (other !is NamedAssertion<*>) {
-            return false
-        }
-        if (name != other.name) {
-            return false
-        }
-        if (isOptional != other.isOptional) {
-            return false
-        }
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = predicate.hashCode()
-        result = 31 * result + name.hashCode()
-        result = 31 * result + isOptional.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/SubjectsParser.kt b/libraries/flicker/src/com/android/server/wm/traces/common/assertions/SubjectsParser.kt
deleted file mode 100644
index e50980b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/assertions/SubjectsParser.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.traces.common.assertions
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-/**
- * Helper class to read traces from a [resultReader] and parse them into subjects for assertion
- *
- * @param resultReader to read the result artifacts
- */
-open class SubjectsParser(private val resultReader: IReader) {
-    fun getSubjects(tag: String): List<FlickerSubject> {
-        val result = mutableListOf<FlickerSubject>()
-
-        if (tag == AssertionTag.ALL) {
-            wmTraceSubject?.let { result.add(it) }
-            layersTraceSubject?.let { result.add(it) }
-        } else {
-            getWmStateSubject(tag)?.let { result.add(it) }
-            getLayerTraceEntrySubject(tag)?.let { result.add(it) }
-        }
-        eventLogSubject?.let { result.add(it) }
-
-        return result
-    }
-
-    /** Truth subject that corresponds to a [WindowManagerTrace] */
-    private val wmTraceSubject: WindowManagerTraceSubject?
-        get() = doGetWmTraceSubject()
-
-    protected open fun doGetWmTraceSubject(): WindowManagerTraceSubject? {
-        val trace = resultReader.readWmTrace() ?: return null
-        return WindowManagerTraceSubject(trace)
-    }
-
-    /** Truth subject that corresponds to a [LayersTrace] */
-    private val layersTraceSubject: LayersTraceSubject?
-        get() = doGetLayersTraceSubject()
-
-    protected open fun doGetLayersTraceSubject(): LayersTraceSubject? {
-        val trace = resultReader.readLayersTrace() ?: return null
-        return LayersTraceSubject(trace)
-    }
-
-    /** Truth subject that corresponds to a [WindowManagerState] */
-    private fun getWmStateSubject(tag: String): WindowManagerStateSubject? =
-        doGetWmStateSubject(tag)
-
-    protected open fun doGetWmStateSubject(tag: String): WindowManagerStateSubject? {
-        return when (tag) {
-            AssertionTag.START -> wmTraceSubject?.subjects?.firstOrNull()
-            AssertionTag.END -> wmTraceSubject?.subjects?.lastOrNull()
-            else -> {
-                val trace = resultReader.readWmState(tag) ?: return null
-                WindowManagerStateSubject(trace.first())
-            }
-        }
-    }
-
-    /** Truth subject that corresponds to a [LayerTraceEntry] */
-    private fun getLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? =
-        doGetLayerTraceEntrySubject(tag)
-
-    protected open fun doGetLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? {
-        return when (tag) {
-            AssertionTag.START -> layersTraceSubject?.subjects?.firstOrNull()
-            AssertionTag.END -> layersTraceSubject?.subjects?.lastOrNull()
-            else -> {
-                val trace = resultReader.readLayersDump(tag) ?: return null
-                return LayersTraceSubject(trace).first()
-            }
-        }
-    }
-
-    /** Truth subject that corresponds to a list of [FocusEvent] */
-    val eventLogSubject: EventLogSubject?
-        get() = doGetEventLogSubject()
-
-    protected open fun doGetEventLogSubject(): EventLogSubject? {
-        val trace = resultReader.readEventLogTrace() ?: return null
-        return EventLogSubject(trace)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/ComponentName.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/ComponentName.kt
deleted file mode 100644
index 83a25bd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/ComponentName.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component
-
-import kotlin.js.JsName
-
-/**
- * Create a new component identifier.
- *
- * This is a version of Android's ComponentName class for flicker. This is necessary because flicker
- * codebase it also compiled into KotlinJS for use into Winscope
- *
- * @param packageName The name of the package that the component exists in. Can not be null.
- * @param className The name of the class inside <var>pkg</var> that implements the component.
- */
-data class ComponentName(override val packageName: String, override val className: String) :
-    IComponentName {
-    /**
-     * Obtains the activity name from the component name.
-     *
-     * See [ComponentName.toWindowName] for additional information
-     */
-    override fun toActivityName(): String {
-        return when {
-            packageName.isNotEmpty() && className.isNotEmpty() -> {
-                val sb = StringBuilder(packageName.length + className.length)
-                appendShortString(sb, packageName, className)
-                return sb.toString()
-            }
-            packageName.isNotEmpty() -> packageName
-            className.isNotEmpty() -> className
-            else -> error("Component name should have an activity of class name")
-        }
-    }
-
-    /**
-     * Obtains the window name from the component name.
-     *
-     * [ComponentName] builds the string representation as PKG/CLASS, however this doesn't work for
-     * system components such as IME, NavBar and StatusBar, Toast.
-     *
-     * If the component doesn't have a package name, assume it's a system component and return only
-     * the class name
-     */
-    override fun toWindowName(): String {
-        return when {
-            packageName.isNotEmpty() && className.isNotEmpty() -> "$packageName/$className"
-            packageName.isNotEmpty() -> packageName
-            className.isNotEmpty() -> className
-            else -> error("Component name should have an activity of class name")
-        }
-    }
-
-    @JsName("toShortWindowName")
-    private fun toShortWindowName(): String {
-        return when {
-            packageName.isNotEmpty() && className.isNotEmpty() ->
-                "$packageName/${className.removePrefix(packageName)}"
-            packageName.isNotEmpty() -> packageName
-            className.isNotEmpty() -> className
-            else -> error("Component name should have an activity of class name")
-        }
-    }
-
-    /**
-     * Obtains the layer name from the component name.
-     *
-     * See [toWindowName] for additional information
-     */
-    override fun toLayerName(): String {
-        var result = this.toWindowName()
-        if (result.contains("/") && !result.contains("#")) {
-            result = "$result#"
-        }
-
-        return result
-    }
-
-    @JsName("appendShortString")
-    private fun appendShortString(sb: StringBuilder, packageName: String, className: String) {
-        sb.append(packageName).append('/')
-        appendShortClassName(sb, packageName, className)
-    }
-
-    @JsName("appendShortClassName")
-    private fun appendShortClassName(sb: StringBuilder, packageName: String, className: String) {
-        if (className.startsWith(packageName)) {
-            val packageNameLength = packageName.length
-            val classNameLength = className.length
-            if (classNameLength > packageNameLength && className[packageNameLength] == '.') {
-                sb.append(className, packageNameLength, classNameLength)
-                return
-            }
-        }
-        sb.append(className)
-    }
-
-    @JsName("toActivityRecordFilter")
-    fun toActivityRecordFilter(): Regex =
-        Regex("ActivityRecord\\{.*${Regex.escape(this.toShortWindowName())}")
-
-    @JsName("toActivityNameRegex")
-    fun toActivityNameRegex(): Regex = Regex(".*${Regex.escape(this.toActivityName())}.*")
-
-    @JsName("toWindowNameRegex")
-    fun toWindowNameRegex(): Regex = Regex(".*${Regex.escape(this.toWindowName())}.*")
-
-    @JsName("toLayerNameRegex")
-    fun toLayerNameRegex(): Regex = Regex(".*${Regex.escape(this.toLayerName())}.*")
-
-    override fun toString(): String = toShortWindowName()
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/IComponentName.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/IComponentName.kt
deleted file mode 100644
index d3e0d45..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/IComponentName.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.traces.common.component
-
-import kotlin.js.JsName
-
-interface IComponentName {
-    @JsName("packageName") val packageName: String
-    @JsName("className") val className: String
-    @JsName("toActivityName") fun toActivityName(): String
-    @JsName("toWindowName") fun toWindowName(): String
-    @JsName("toLayerName") fun toLayerName(): String
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentNameMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentNameMatcher.kt
deleted file mode 100644
index 59074cb..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentNameMatcher.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.component.ComponentName
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import kotlin.js.JsName
-
-/** ComponentMatcher based on name */
-class ComponentNameMatcher(var component: ComponentName) : IComponentNameMatcher {
-    override val packageName: String
-        get() = component.packageName
-    override val className: String
-        get() = component.className
-    override fun toActivityName(): String = component.toActivityName()
-    override fun toWindowName(): String = component.toWindowName()
-    override fun toLayerName(): String = component.toLayerName()
-
-    constructor(
-        packageName: String,
-        className: String
-    ) : this(ComponentName(packageName, className))
-
-    constructor(className: String) : this("", className)
-
-    @JsName("matchesAnyOf")
-    private fun <T> matchesAnyOf(
-        values: Array<T>,
-        valueProducer: (T) -> String,
-        regexProducer: (ComponentName) -> Regex,
-    ): Boolean {
-        val componentRegex = regexProducer.invoke(component)
-        val targets = values.map { valueProducer.invoke(it) }
-        return targets.any { value -> componentRegex.matches(value) }
-    }
-
-    override fun componentNameMatcherToString(): String {
-        return "ComponentNameMatcher(\"${this.packageName}\", " + "\"${this.className}\")"
-    }
-
-    /** {@inheritDoc} */
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean =
-        matchesAnyOf(windows, { it.title }, { it.toWindowNameRegex() })
-
-    /** {@inheritDoc} */
-    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean =
-        matchesAnyOf(activities, { it.name }, { it.toActivityNameRegex() })
-
-    /** {@inheritDoc} */
-    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean =
-        matchesAnyOf(layers, { it.name }, { it.toLayerNameRegex() })
-
-    /** {@inheritDoc} */
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
-
-    /** {@inheritDoc} */
-    override fun toActivityIdentifier(): String = component.toActivityName()
-
-    /** {@inheritDoc} */
-    override fun toWindowIdentifier(): String = component.toWindowName()
-
-    /** {@inheritDoc} */
-    override fun toLayerIdentifier(): String = component.toLayerName()
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ComponentNameMatcher) return false
-        return component == other.component
-    }
-
-    override fun hashCode(): Int = component.hashCode()
-
-    override fun toString(): String = component.toString()
-
-    companion object {
-        @JsName("NAV_BAR") val NAV_BAR = ComponentNameMatcher("", "NavigationBar0")
-        @JsName("TASK_BAR") val TASK_BAR = ComponentNameMatcher("", "Taskbar")
-        @JsName("STATUS_BAR") val STATUS_BAR = ComponentNameMatcher("", "StatusBar")
-        @JsName("ROTATION") val ROTATION = ComponentNameMatcher("", "RotationLayer")
-        @JsName("BACK_SURFACE") val BACK_SURFACE = ComponentNameMatcher("", "BackColorSurface")
-        @JsName("IME") val IME = ComponentNameMatcher("", "InputMethod")
-        @JsName("IME_SNAPSHOT") val IME_SNAPSHOT = ComponentNameMatcher("", "IME-snapshot-surface")
-        @JsName("SPLASH_SCREEN") val SPLASH_SCREEN = ComponentNameMatcher("", "Splash Screen")
-        @JsName("SNAPSHOT") val SNAPSHOT = ComponentNameMatcher("", "SnapshotStartingWindow")
-        @JsName("TRANSITION_SNAPSHOT")
-        val TRANSITION_SNAPSHOT = ComponentNameMatcher("", "transition snapshot")
-        @JsName("LETTERBOX") val LETTERBOX = ComponentNameMatcher("", "Letterbox")
-        @JsName("WALLPAPER_BBQ_WRAPPER")
-        val WALLPAPER_BBQ_WRAPPER = ComponentNameMatcher("", "Wallpaper BBQ wrapper")
-        @JsName("PIP_CONTENT_OVERLAY")
-        val PIP_CONTENT_OVERLAY = ComponentNameMatcher("", "PipContentOverlay")
-        @JsName("LAUNCHER")
-        val LAUNCHER =
-            ComponentNameMatcher(
-                "com.google.android.apps.nexuslauncher",
-                "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
-            )
-        @JsName("AOSP_LAUNCHER")
-        val AOSP_LAUNCHER =
-            ComponentNameMatcher(
-                "com.android.launcher3",
-                "com.android.launcher3.uioverrides.QuickstepLauncher"
-            )
-        @JsName("SPLIT_DIVIDER")
-        val SPLIT_DIVIDER = ComponentNameMatcher("", "StageCoordinatorSplitDivider")
-        @JsName("DEFAULT_TASK_DISPLAY_AREA")
-        val DEFAULT_TASK_DISPLAY_AREA = ComponentNameMatcher("", "DefaultTaskDisplayArea")
-
-        /**
-         * Creates a component matcher from a window or layer name.
-         *
-         * Requires the [str] to contain both the package and class name (with a / separator)
-         *
-         * @param str Value to parse
-         */
-        @JsName("unflattenFromString")
-        fun unflattenFromString(str: String): ComponentNameMatcher {
-            val sep = str.indexOf('/')
-            if (sep < 0 || sep + 1 >= str.length) {
-                error("Missing package/class separator")
-            }
-            val pkg = str.substring(0, sep)
-            var cls = str.substring(sep + 1)
-            if (cls.isNotEmpty() && cls[0] == '.') {
-                cls = pkg + cls
-            }
-            return ComponentNameMatcher(pkg, cls)
-        }
-
-        /**
-         * Creates a component matcher from a window or layer name. The name might contain junk,
-         * which will be removed to only extract package and class name (e.g. other words before
-         * package name, separated by spaces, #id in the end after the class name)
-         *
-         * Requires the [str] to contain both the package and class name (with a / separator)
-         *
-         * @param str Value to parse
-         */
-        @JsName("unflattenFromStringWithJunk")
-        fun unflattenFromStringWithJunk(str: String): ComponentNameMatcher {
-            val sep = str.indexOf('/')
-            if (sep < 0 || sep + 1 >= str.length) {
-                error("Missing package/class separator")
-            }
-
-            var pkg = str.substring(0, sep)
-            var pkgSep: Int = -1
-            val pkgCharArr = pkg.toCharArray()
-            for (index in (0..pkgCharArr.lastIndex).reversed()) {
-                val currentChar = pkgCharArr[index]
-                if (currentChar !in 'A'..'Z' && currentChar !in 'a'..'z' && currentChar != '.') {
-                    pkgSep = index
-                    break
-                }
-            }
-            if (!(pkgSep < 0 || pkgSep + 1 >= pkg.length)) {
-                pkg = pkg.substring(pkgSep, pkg.length)
-            }
-
-            var cls = str.substring(sep + 1)
-            var clsSep = -1 // cls.indexOf('#')
-            val clsCharArr = cls.toCharArray()
-            for (index in (0..clsCharArr.lastIndex)) {
-                val currentChar = clsCharArr[index]
-                if (currentChar !in 'A'..'Z' && currentChar !in 'a'..'z' && currentChar != '.') {
-                    clsSep = index
-                    break
-                }
-            }
-            if (!(clsSep < 0 || clsSep + 1 >= cls.length)) {
-                cls = cls.substring(0, clsSep)
-            }
-
-            if (cls.isNotEmpty() && cls[0] == '.') {
-                cls = pkg + cls
-            }
-            return ComponentNameMatcher(pkg, cls)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentSplashScreenMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentSplashScreenMatcher.kt
deleted file mode 100644
index fe5d86c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ComponentSplashScreenMatcher.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-class ComponentSplashScreenMatcher(val componentNameMatcher: ComponentNameMatcher) :
-    IComponentMatcher {
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
-        error("Unimplemented - There are no splashscreen windows")
-    }
-
-    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
-        error("Unimplemented - There are no splashscreen windows")
-    }
-
-    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
-        return layers.any {
-            if (!it.name.contains("Splash Screen")) {
-                return@any false
-            }
-            if (it.children.isNotEmpty()) {
-                // Not leaf splash screen layer but container of the splash screen layer
-                return@any false
-            }
-            val grandParent = it.parent?.parent
-            requireNotNull(grandParent) { "Splash screen layer's grandparent shouldn't be null" }
-            return@any componentNameMatcher.layerMatchesAnyOf(grandParent)
-        }
-    }
-
-    override fun toActivityIdentifier(): String {
-        error("Unimplemented - There are no splashscreen windows")
-    }
-
-    override fun toWindowIdentifier(): String {
-        error("Unimplemented - There are no splashscreen windows")
-    }
-
-    override fun toLayerIdentifier(): String {
-        return "Splash Screen ${componentNameMatcher.className}"
-    }
-
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean {
-        val splashScreenLayer = layers.filter { layerMatchesAnyOf(it) }
-        require(splashScreenLayer.size < 1) {
-            "More than on SplashScreen layer found. Only up to 1 match was expected."
-        }
-        return condition(splashScreenLayer)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/EdgeExtensionComponentMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/EdgeExtensionComponentMatcher.kt
deleted file mode 100644
index 5477d3d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/EdgeExtensionComponentMatcher.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-class EdgeExtensionComponentMatcher : IComponentMatcher {
-    /** {@inheritDoc} */
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
-        // Doesn't have a window component only layers
-        return false
-    }
-
-    /** {@inheritDoc} */
-    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
-        // Doesn't have a window component only layers
-        return false
-    }
-
-    /** {@inheritDoc} */
-    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
-        return layers.any {
-            if (it.name.contains("bbq-wrapper")) {
-                val parent = it.parent ?: return false
-                return layerMatchesAnyOf(parent)
-            }
-
-            return it.name.contains("Left Edge Extension") ||
-                it.name.contains("Right Edge Extension")
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
-
-    /** {@inheritDoc} */
-    override fun toActivityIdentifier(): String {
-        throw NotImplementedError(
-            "toActivityIdentifier() is not implemented on EdgeExtensionComponentMatcher"
-        )
-    }
-
-    /** {@inheritDoc} */
-    override fun toWindowIdentifier(): String {
-        throw NotImplementedError(
-            "toWindowName() is not implemented on EdgeExtensionComponentMatcher"
-        )
-    }
-
-    /** {@inheritDoc} */
-    override fun toLayerIdentifier(): String {
-        return "EdgeExtensionLayer"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ExactComponentIdMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ExactComponentIdMatcher.kt
deleted file mode 100644
index ee950d5..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/ExactComponentIdMatcher.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-/**
- * A component matcher that matches the targeted window and layer with ids windowId and layerId
- * respectively.
- *
- * No other windows or layers will be matched, if you want to also match the children of that window
- * and layer then use FullComponentIdMatcher instead.
- */
-class ExactComponentIdMatcher(private val windowId: Int, private val layerId: Int) :
-    IComponentMatcher {
-    /**
-     * @return if any of the [components] matches any of [windows]
-     *
-     * @param windows to search
-     */
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>) =
-        windows.any { it.token == windowId.toString(16) }
-
-    /**
-     * @return if any of the [components] matches any of [activities]
-     *
-     * @param activities to search
-     */
-    override fun activityMatchesAnyOf(activities: Array<Activity>) =
-        activities.any { it.token == windowId.toString(16) }
-
-    /**
-     * @return if any of the [components] matches any of [layers]
-     *
-     * @param layers to search
-     */
-    override fun layerMatchesAnyOf(layers: Array<Layer>) = layers.any { it.id == layerId }
-
-    /** {@inheritDoc} */
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean = condition(layers.filter { it.id == layerId })
-
-    /** {@inheritDoc} */
-    override fun toActivityIdentifier() = toWindowIdentifier()
-
-    /** {@inheritDoc} */
-    override fun toWindowIdentifier() = "Window#${windowId.toString(16)}"
-
-    /** {@inheritDoc} */
-    override fun toLayerIdentifier(): String = "Layer#$layerId"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/FullComponentIdMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/FullComponentIdMatcher.kt
deleted file mode 100644
index cd2a623..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/FullComponentIdMatcher.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-/**
- * A component matcher that matches the targeted window and layer with ids windowId and layerId
- * respectively and all their children windows and layers.
- *
- * If you want to only match the window and layer with the specified ids then use
- * ExactComponentIdMatcher instead.
- */
-class FullComponentIdMatcher(val windowId: Int, val layerId: Int) : IComponentMatcher {
-    /**
-     * @return if any of the [components] matches any of [windows]
-     *
-     * @param windows to search
-     */
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean =
-        windows.any {
-            val parent = it.parent
-            when {
-                it.token == windowId.toString(16) -> true
-                parent != null -> windowMatchesAnyOf(parent)
-                else -> false
-            }
-        }
-
-    /**
-     * @return if any of the [components] matches any of [activities]
-     *
-     * @param activities to search
-     */
-    override fun activityMatchesAnyOf(activities: Array<Activity>) =
-        activities.any { it.token == windowId.toString(16) }
-
-    /**
-     * @return if any of the [components] matches any of [layers]
-     *
-     * @param layers to search
-     */
-    override fun layerMatchesAnyOf(layers: Array<Layer>) =
-        layers.any {
-            val parent = it.parent
-            when {
-                it.id == layerId -> true
-                parent != null -> layerMatchesAnyOf(parent)
-                else -> false
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean = condition(layers.filter { layerMatchesAnyOf(it) })
-
-    /** {@inheritDoc} */
-    override fun toActivityIdentifier() = toWindowIdentifier()
-
-    /** {@inheritDoc} */
-    override fun toWindowIdentifier() = "Window#${windowId.toString(16)} & children"
-
-    /** {@inheritDoc} */
-    override fun toLayerIdentifier(): String = "Layer#$layerId & children"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentMatcher.kt
deleted file mode 100644
index 4ffe3e3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentMatcher.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import kotlin.js.JsName
-
-interface IComponentMatcher {
-    @JsName("or")
-    fun or(other: IComponentMatcher): IComponentMatcher {
-        return OrComponentMatcher(arrayOf(this, other))
-    }
-
-    /**
-     * @return if any of the [components] matches [window]
-     *
-     * @param window to search
-     */
-    fun windowMatchesAnyOf(window: WindowContainer): Boolean = windowMatchesAnyOf(arrayOf(window))
-
-    /**
-     * @return if any of the [components] matches any of [windows]
-     *
-     * @param windows to search
-     */
-    fun windowMatchesAnyOf(windows: Collection<WindowContainer>): Boolean =
-        windowMatchesAnyOf(windows.toTypedArray())
-
-    /**
-     * @return if any of the [windows] fit the matching conditions of the matcher
-     *
-     * @param windows to search
-     */
-    fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean
-
-    /**
-     * @return if any of the [components] matches [activity]
-     *
-     * @param activity to search
-     */
-    fun activityMatchesAnyOf(activity: Activity): Boolean = activityMatchesAnyOf(arrayOf(activity))
-
-    /**
-     * @return if any of the [components] matches any of [activities]
-     *
-     * @param activities to search
-     */
-    fun activityMatchesAnyOf(activities: Collection<Activity>): Boolean =
-        activityMatchesAnyOf(activities.toTypedArray())
-
-    /**
-     * @return if any of the [components] matches any of [activities]
-     *
-     * @param activities to search
-     */
-    fun activityMatchesAnyOf(activities: Array<Activity>): Boolean
-
-    /**
-     * @return if any of the [components] matches [layer]
-     *
-     * @param layer to search
-     */
-    fun layerMatchesAnyOf(layer: Layer): Boolean = layerMatchesAnyOf(arrayOf(layer))
-
-    /**
-     * @return if any of the [components] matches any of [layers]
-     *
-     * @param layers to search
-     */
-    fun layerMatchesAnyOf(layers: Collection<Layer>): Boolean =
-        layerMatchesAnyOf(layers.toTypedArray())
-
-    fun layerMatchesAnyOf(layers: Array<Layer>): Boolean
-
-    /**
-     * @return an identifier string that provides enough information to determine which activities
-     * ```
-     *         the matcher is looking to match. Mostly used for debugging purposes in error messages
-     * ```
-     */
-    fun toActivityIdentifier(): String
-
-    /**
-     * @return an identifier string that provides enough information to determine which windows the
-     * ```
-     *         matcher is looking to match. Mostly used for debugging purposes in error messages.
-     * ```
-     */
-    @JsName("toWindowIdentifier") fun toWindowIdentifier(): String
-
-    /**
-     * @return an identifier string that provides enough information to determine which layers the
-     * ```
-     *         matcher is looking to match. Mostly used for debugging purposes in error messages.
-     * ```
-     */
-    @JsName("toLayerIdentifier") fun toLayerIdentifier(): String
-
-    /**
-     * @param layers Collection of layers check for matches
-     * @param condition A function taking the matched layers of a base level component and returning
-     * ```
-     *              true or false base on if the check succeeded.
-     * @return
-     * ```
-     * true iff all the check condition is satisfied according to the ComponentMatcher's
-     * ```
-     *         defined execution of it.
-     * ```
-     */
-    @JsName("check")
-    fun check(layers: Collection<Layer>, condition: (Collection<Layer>) -> Boolean): Boolean
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentNameMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentNameMatcher.kt
deleted file mode 100644
index 7043f80..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/IComponentNameMatcher.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.component.IComponentName
-
-interface IComponentNameMatcher : IComponentMatcher, IComponentName {
-    fun componentNameMatcherToString(): String
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/OrComponentMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/OrComponentMatcher.kt
deleted file mode 100644
index e1afe83..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/component/matchers/OrComponentMatcher.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.component.matchers
-
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-class OrComponentMatcher(private val componentMatchers: Array<out IComponentMatcher>) :
-    IComponentMatcher {
-
-    /** {@inheritDoc} */
-    override fun windowMatchesAnyOf(window: WindowContainer): Boolean {
-        return componentMatchers.any { it.windowMatchesAnyOf(window) }
-    }
-
-    /** {@inheritDoc} */
-    override fun windowMatchesAnyOf(windows: Collection<WindowContainer>): Boolean {
-        return componentMatchers.any { it.windowMatchesAnyOf(windows) }
-    }
-
-    /** {@inheritDoc} */
-    override fun windowMatchesAnyOf(windows: Array<WindowContainer>): Boolean {
-        return componentMatchers.any { it.windowMatchesAnyOf(windows) }
-    }
-
-    /** {@inheritDoc} */
-    override fun activityMatchesAnyOf(activity: Activity): Boolean {
-        return componentMatchers.any { it.activityMatchesAnyOf(activity) }
-    }
-
-    /** {@inheritDoc} */
-    override fun activityMatchesAnyOf(activities: Collection<Activity>): Boolean {
-        return componentMatchers.any { it.activityMatchesAnyOf(activities) }
-    }
-
-    /** {@inheritDoc} */
-    override fun activityMatchesAnyOf(activities: Array<Activity>): Boolean {
-        return componentMatchers.any { it.activityMatchesAnyOf(activities) }
-    }
-
-    /** {@inheritDoc} */
-    override fun layerMatchesAnyOf(layer: Layer): Boolean {
-        return componentMatchers.any { it.layerMatchesAnyOf(layer) }
-    }
-
-    /** {@inheritDoc} */
-    override fun layerMatchesAnyOf(layers: Collection<Layer>): Boolean {
-        return componentMatchers.any { it.layerMatchesAnyOf(layers) }
-    }
-
-    /** {@inheritDoc} */
-    override fun layerMatchesAnyOf(layers: Array<Layer>): Boolean {
-        return componentMatchers.any { it.layerMatchesAnyOf(layers) }
-    }
-
-    /** {@inheritDoc} */
-    override fun check(
-        layers: Collection<Layer>,
-        condition: (Collection<Layer>) -> Boolean
-    ): Boolean {
-        return componentMatchers.any { oredComponent ->
-            oredComponent.check(layers) { condition(it) }
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun toActivityIdentifier(): String =
-        componentMatchers.joinToString(" or ") { it.toActivityIdentifier() }
-
-    /** {@inheritDoc} */
-    override fun toWindowIdentifier(): String =
-        componentMatchers.joinToString(" or ") { it.toWindowIdentifier() }
-
-    /** {@inheritDoc} */
-    override fun toLayerIdentifier(): String =
-        componentMatchers.joinToString(" or ") { it.toLayerIdentifier() }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt
deleted file mode 100644
index f21332f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/errors/Error.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.errors
-
-/**
- * Flicker Error identified in a WindowManager or SurfaceFlinger trace
- * @param stacktrace Stacktrace to identify source of errors
- * @param message Message to explain error briefly
- * @param layerId The layer which the error is associated with
- * @param windowToken The window which the error is associated with
- * @param taskId The task which the error is associated with
- * @param assertionName The class name of the assertion that generated the error
- */
-data class Error(
-    val stacktrace: String,
-    val message: String,
-    val layerId: Int = 0,
-    val windowToken: String = "",
-    val taskId: Int = 0,
-    val assertionName: String = ""
-)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt
deleted file mode 100644
index 54ed7a4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorState.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.android.server.wm.traces.common.errors
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-
-/**
- * A state at a particular time within a trace that holds a list of errors there may be.
- * @param errors Errors contained in the state
- * @param _timestamp Timestamp of this state
- */
-class ErrorState(val errors: Array<Error>, override val timestamp: Timestamp) : ITraceEntry {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ErrorState) return false
-        if (timestamp != other.timestamp) return false
-        if (errors.contentEquals(other.errors)) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = timestamp.hashCode()
-        result = 31 * result + errors.contentDeepHashCode()
-        return result
-    }
-
-    override fun toString(): String =
-        "FlickerErrorState(" +
-            "timestamp=$timestamp}, numberOfErrors=${errors.size} " +
-            "${errors.joinToString("\n") { it.assertionName }})"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt
deleted file mode 100644
index a69bdd4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/errors/ErrorTrace.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.errors
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-
-/**
- * Represents all the states with errors in an entire trace.
- * @param entries States with errors contained in this trace
- */
-data class ErrorTrace(override val entries: Array<ErrorState>) :
-    ITrace<ErrorState>, List<ErrorState> by entries.toList() {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ErrorTrace) return false
-        if (!entries.contentEquals(other.entries)) return false
-        return true
-    }
-
-    override fun hashCode(): Int = entries.contentDeepHashCode()
-
-    override fun toString(): String =
-        "FlickerErrorTrace(First: ${entries.firstOrNull()}," + "End: ${entries.lastOrNull()})"
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ErrorTrace {
-        return ErrorTrace(
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/Cuj.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/Cuj.kt
deleted file mode 100644
index 4d0ee03..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/Cuj.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-
-data class Cuj(
-    val cuj: CujType,
-    val startTimestamp: Timestamp,
-    val endTimestamp: Timestamp,
-    val canceled: Boolean
-) : ITraceEntry {
-    override val timestamp: Timestamp = startTimestamp
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujEvent.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/CujEvent.kt
deleted file mode 100644
index 91431c0..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujEvent.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-
-/**
- * Represents a CUJ Event from the [EventLog]
- *
- * {@inheritDoc}
- */
-class CujEvent(
-    timestamp: Timestamp,
-    val cuj: CujType,
-    processId: Int,
-    uid: String,
-    threadId: Int,
-    tag: String
-) : Event(timestamp, processId, uid, threadId, tag) {
-
-    val type: Type =
-        when (tag) {
-            JANK_CUJ_BEGIN_TAG -> Type.START
-            JANK_CUJ_END_TAG -> Type.END
-            JANK_CUJ_CANCEL_TAG -> Type.CANCEL
-            else -> error("Unhandled tag type")
-        }
-
-    constructor(
-        timestamp: Timestamp,
-        processId: Int,
-        uid: String,
-        threadId: Int,
-        tag: String,
-        data: String
-    ) : this(
-        CrossPlatform.timestamp.from(
-            elapsedNanos = getElapsedTimestampFromData(data),
-            systemUptimeNanos = getSystemUptimeNanosFromData(data),
-            unixNanos = timestamp.unixNanos
-        ),
-        getCujMarkerFromData(data),
-        processId,
-        uid,
-        threadId,
-        tag
-    )
-
-    override fun toString(): String {
-        return "CujEvent(" +
-            "timestamp=$timestamp, " +
-            "cuj=$cuj, " +
-            "processId=$processId, " +
-            "uid=$uid, " +
-            "threadId=$threadId, " +
-            "tag=$tag" +
-            ")"
-    }
-
-    companion object {
-        private fun getCujMarkerFromData(data: String): CujType {
-            val dataEntries = getDataEntries(data)
-            val eventId = dataEntries[0].toInt()
-            return CujType.from(eventId)
-        }
-
-        private fun getElapsedTimestampFromData(data: String): Long {
-            val dataEntries = getDataEntries(data)
-            return dataEntries[1].toLong()
-        }
-
-        private fun getSystemUptimeNanosFromData(data: String): Long {
-            val dataEntries = getDataEntries(data)
-            return dataEntries[2].toLong()
-        }
-
-        private fun getDataEntries(data: String): List<String> {
-            require("""\[[0-9]+,[0-9]+,[0-9]+]""".toRegex().matches(data)) {
-                "Data ($data) didn't match expected format"
-            }
-
-            return data.slice(1..data.length - 2).split(",")
-        }
-
-        enum class Type {
-            START,
-            END,
-            CANCEL
-        }
-
-        const val JANK_CUJ_BEGIN_TAG = "jank_cuj_events_begin_request"
-        const val JANK_CUJ_END_TAG = "jank_cuj_events_end_request"
-        const val JANK_CUJ_CANCEL_TAG = "jank_cuj_events_cancel_request"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/CujTrace.kt
deleted file mode 100644
index 79393eb..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujTrace.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-
-class CujTrace(override val entries: Array<Cuj>) : ITrace<Cuj>, List<Cuj> by entries.toList() {
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): CujTrace {
-        return CujTrace(
-            entries
-                .dropWhile { it.endTimestamp < startTimestamp }
-                .dropLastWhile { it.startTimestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-
-    companion object {
-        fun from(cujEvents: Array<CujEvent>): CujTrace {
-            val cujs = mutableListOf<Cuj>()
-
-            val sortedCujEvents = cujEvents.sortedBy { it.timestamp.unixNanos }
-            val startEvents = sortedCujEvents.filter { it.type == CujEvent.Companion.Type.START }
-            val endEvents = sortedCujEvents.filter { it.type == CujEvent.Companion.Type.END }
-            val canceledEvents =
-                sortedCujEvents.filter { it.type == CujEvent.Companion.Type.CANCEL }
-
-            for (startEvent in startEvents) {
-                val matchingEndEvent =
-                    endEvents.firstOrNull {
-                        it.cuj == startEvent.cuj && it.timestamp >= startEvent.timestamp
-                    }
-                val matchingCancelEvent =
-                    canceledEvents.firstOrNull {
-                        it.cuj == startEvent.cuj && it.timestamp >= startEvent.timestamp
-                    }
-
-                if (matchingCancelEvent == null && matchingEndEvent == null) {
-                    // CUJ started but not ended within the trace
-                    continue
-                }
-
-                val closingEvent =
-                    listOf(matchingCancelEvent, matchingEndEvent).minBy {
-                        it?.timestamp ?: CrossPlatform.timestamp.max()
-                    }
-                        ?: error("Should have found one matching closing event")
-                val canceled = closingEvent.type == CujEvent.Companion.Type.CANCEL
-
-                cujs.add(
-                    Cuj(startEvent.cuj, startEvent.timestamp, closingEvent.timestamp, canceled)
-                )
-            }
-
-            return CujTrace(cujs.toTypedArray())
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujType.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/CujType.kt
deleted file mode 100644
index 47ad662..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/CujType.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.traces.common.events
-
-/**
- * From com.android.internal.jank.InteractionJankMonitor.
- *
- * NOTE: Make sure order is the same as in {@see com.android.internal.jank.InteractionJankMonitor}.
- */
-enum class CujType {
-    CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
-    CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
-    CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
-    CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
-    CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
-    CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
-    CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
-    CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-    CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
-    CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
-    CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
-    CUJ_LAUNCHER_QUICK_SWITCH,
-    CUJ_NOTIFICATION_HEADS_UP_APPEAR,
-    CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
-    CUJ_NOTIFICATION_ADD,
-    CUJ_NOTIFICATION_REMOVE,
-    CUJ_NOTIFICATION_APP_START,
-    CUJ_LOCKSCREEN_PASSWORD_APPEAR,
-    CUJ_LOCKSCREEN_PATTERN_APPEAR,
-    CUJ_LOCKSCREEN_PIN_APPEAR,
-    CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
-    CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
-    CUJ_LOCKSCREEN_PIN_DISAPPEAR,
-    CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
-    CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
-    CUJ_LAUNCHER_OPEN_ALL_APPS,
-    CUJ_LAUNCHER_ALL_APPS_SCROLL,
-    CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
-    CUJ_SETTINGS_PAGE_SCROLL,
-    CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
-    CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
-    CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
-    CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
-    CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
-    CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-    CUJ_PIP_TRANSITION,
-    CUJ_WALLPAPER_TRANSITION,
-    CUJ_USER_SWITCH,
-    CUJ_SPLASHSCREEN_AVD,
-    CUJ_SPLASHSCREEN_EXIT_ANIM,
-    CUJ_SCREEN_OFF,
-    CUJ_SCREEN_OFF_SHOW_AOD,
-    CUJ_ONE_HANDED_ENTER_TRANSITION,
-    CUJ_ONE_HANDED_EXIT_TRANSITION,
-    CUJ_UNFOLD_ANIM,
-    CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
-    CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
-    CUJ_SUW_LOADING_TO_NEXT_FLOW,
-    CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
-    CUJ_SPLIT_SCREEN_ENTER,
-    CUJ_SPLIT_SCREEN_EXIT,
-    CUJ_LOCKSCREEN_LAUNCH_CAMERA,
-    CUJ_SPLIT_SCREEN_RESIZE,
-    CUJ_SETTINGS_SLIDER,
-    CUJ_TAKE_SCREENSHOT,
-    CUJ_VOLUME_CONTROL,
-    CUJ_BIOMETRIC_PROMPT_TRANSITION,
-    CUJ_SETTINGS_TOGGLE,
-    CUJ_SHADE_DIALOG_OPEN,
-    CUJ_USER_DIALOG_OPEN,
-    CUJ_TASKBAR_EXPAND,
-    CUJ_TASKBAR_COLLAPSE,
-    CUJ_SHADE_CLEAR_ALL,
-    CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-    CUJ_LOCKSCREEN_OCCLUSION,
-    CUJ_RECENTS_SCROLLING,
-    CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
-    CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-    CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-
-    // KEEP AS LAST TYPE
-    // used to handle new types that haven't been added here yet but might be dumped by the platform
-    UNKNOWN;
-
-    companion object {
-        fun from(eventId: Int): CujType {
-            // -1 to account for unknown event type
-            if (eventId >= values().size - 1) {
-                return UNKNOWN
-            }
-            return values()[eventId]
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/Event.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/Event.kt
deleted file mode 100644
index 9167d6b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/Event.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-
-/**
- * Represents an Event from the [EventLog]
- *
- * @param timestamp The wall clock time in nanoseconds when the entry was written.
- * @param processId The process ID which wrote the log entry
- * @param uid The UID which wrote the log entry, special UIDs are strings instead of numbers (e.g.
- * root)
- * @param threadId The thread which wrote the log entry
- * @param tag The type tag code of the entry
- */
-open class Event(
-    override val timestamp: Timestamp,
-    val processId: Int,
-    val uid: String,
-    val threadId: Int,
-    val tag: String
-) : ITraceEntry
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/EventLog.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/EventLog.kt
deleted file mode 100644
index a9bcd2d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/EventLog.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-/**
- * Represents the data from the Android EventLog and contains a collection of parsed events of
- * interest.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class EventLog(override val entries: Array<Event>) :
-    ITrace<Event>, List<Event> by entries.toList() {
-    @JsName("focusEvents")
-    val focusEvents: Array<FocusEvent> =
-        entries
-            .filterIsInstance<FocusEvent>()
-            .filter { it.type !== FocusEvent.Type.REQUESTED }
-            .toTypedArray()
-
-    @JsName("cujEvents")
-    val cujEvents: Array<CujEvent> = entries.filterIsInstance<CujEvent>().toTypedArray()
-
-    @JsName("cujTrace") val cujTrace: CujTrace = CujTrace.from(cujEvents)
-
-    companion object {
-        const val MAGIC_NUMBER = "EventLog"
-    }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): EventLog {
-        return EventLog(
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/events/FocusEvent.kt b/libraries/flicker/src/com/android/server/wm/traces/common/events/FocusEvent.kt
deleted file mode 100644
index d23c804..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/events/FocusEvent.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.traces.common.Timestamp
-
-/**
- * An Event from the [EventLog] representing a window focus change or request.
- *
- * @param timestamp The wall clock time in nanoseconds when the entry was written.
- * @param window The window that is the target of the focus event.
- * @param type The type of focus event this is.
- * @param reason The reason for the focus event.
- * @param processId The process ID which wrote the log entry
- * @param uid The UID which wrote the log entry, special UIDs are strings instead of numbers (e.g.
- * root)
- * @param threadId The thread which wrote the log entry
- * @param tag The type tag code of the entry
- */
-class FocusEvent(
-    timestamp: Timestamp,
-    val window: String,
-    val type: Type,
-    val reason: String,
-    processId: Int,
-    uid: String,
-    threadId: Int
-) : Event(timestamp, processId, uid, threadId, INPUT_FOCUS_TAG) {
-    enum class Type {
-        GAINED,
-        LOST,
-        REQUESTED
-    }
-
-    constructor(
-        timestamp: Timestamp,
-        processId: Int,
-        uid: String,
-        threadId: Int,
-        data: Array<String>
-    ) : this(
-        timestamp,
-        getWindowFromData(data[0]),
-        getFocusFromData(data[0]),
-        data[1].removePrefix("reason="),
-        processId,
-        uid,
-        threadId
-    )
-
-    override fun toString(): String {
-        return "$timestamp: Focus ${type.name} $window Reason=$reason"
-    }
-
-    fun hasFocus(): Boolean {
-        return this.type == Type.GAINED
-    }
-
-    companion object {
-        private fun getWindowFromData(data: String): String {
-            var expectedWhiteSpace = 2
-            return data.dropWhile { !it.isWhitespace() || --expectedWhiteSpace > 0 }.drop(1)
-        }
-
-        private fun getFocusFromData(data: String): Type {
-            return when {
-                data.contains(" entering ") -> Type.GAINED
-                data.contains(" leaving ") -> Type.LOST
-                else -> Type.REQUESTED
-            }
-        }
-
-        const val INPUT_FOCUS_TAG = "input_focus"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/Consts.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/Consts.kt
deleted file mode 100644
index a8265db..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/Consts.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.traces.common.io
-
-import com.android.server.wm.traces.common.FLICKER_TAG
-
-const val WINSCOPE_EXT = ".winscope"
-const val FLICKER_IO_TAG = "$FLICKER_TAG-IO"
-const val BUFFER_SIZE = 2048
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/IReader.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/IReader.kt
deleted file mode 100644
index 1923f5f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/IReader.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.io
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.CujTrace
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-/** Helper class to read results from a flicker artifact */
-interface IReader {
-    val artifactPath: String
-    val executionError: Throwable?
-    val runStatus: RunStatus
-    val isFailure: Boolean
-        get() = runStatus.isFailure
-
-    /** @return a [WindowManagerTrace] from the dump associated to [tag] */
-    fun readWmState(tag: String): WindowManagerTrace?
-
-    /** @return a [WindowManagerTrace] for the part of the trace we want to run the assertions on */
-    fun readWmTrace(): WindowManagerTrace?
-
-    /** @return a [LayersTrace] for the part of the trace we want to run the assertions on */
-    fun readLayersTrace(): LayersTrace?
-
-    /** @return a [LayersTrace] from the dump associated to [tag] */
-    fun readLayersDump(tag: String): LayersTrace?
-
-    /** @return a [TransactionsTrace] for the part of the trace we want to run the assertions on */
-    fun readTransactionsTrace(): TransactionsTrace?
-
-    /** @return a [TransitionsTrace] for the part of the trace we want to run the assertions on */
-    fun readTransitionsTrace(): TransitionsTrace?
-
-    /** @return an [EventLog] for the part of the trace we want to run the assertions on */
-    fun readEventLogTrace(): EventLog?
-
-    /** @return a [CujTrace] for the part of the trace we want to run the assertions on */
-    fun readCujTrace(): CujTrace?
-
-    /** @return an [IReader] for the subsection of the trace we are reading in this reader */
-    fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): IReader
-
-    /**
-     * @return [ByteArray] with the contents of a file from the artifact, or null if the file
-     * doesn't exist
-     */
-    fun readBytes(traceType: TraceType, tag: String = AssertionTag.ALL): ByteArray?
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/ResultArtifactDescriptor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/ResultArtifactDescriptor.kt
deleted file mode 100644
index 3df7da8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/ResultArtifactDescriptor.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.io
-
-import com.android.server.wm.traces.common.AssertionTag
-
-/** Descriptor for files inside flicker result artifacts */
-class ResultArtifactDescriptor(
-    /** Trace or dump type */
-    val traceType: TraceType,
-    /** If the trace/dump is associated with a tag */
-    val tag: String = AssertionTag.ALL
-) {
-    private val isTagTrace: Boolean
-        get() = tag != AssertionTag.ALL
-
-    /** Name of the trace file in the result artifact (e.g. zip) */
-    val fileNameInArtifact: String = buildString {
-        if (isTagTrace) {
-            append(tag)
-            append("__")
-        }
-        append(traceType.fileName)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ResultArtifactDescriptor) return false
-
-        if (traceType != other.traceType) return false
-        if (tag != other.tag) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = traceType.hashCode()
-        result = 31 * result + tag.hashCode()
-        return result
-    }
-
-    override fun toString(): String = fileNameInArtifact
-
-    companion object {
-        /**
-         * Creates a [ResultArtifactDescriptor] based on the [fileNameInArtifact]
-         *
-         * @param fileNameInArtifact Name of the trace file in the result artifact (e.g. zip)
-         */
-        fun fromFileName(fileNameInArtifact: String): ResultArtifactDescriptor {
-            val tagSplit = fileNameInArtifact.split("__")
-            require(tagSplit.size <= 2) {
-                "File name format should match '{tag}__{filename}' but was $fileNameInArtifact"
-            }
-            val tag = if (tagSplit.size > 1) tagSplit.first() else AssertionTag.ALL
-            val fileName = tagSplit.last()
-            return ResultArtifactDescriptor(TraceType.fromFileName(fileName), tag)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/RunStatus.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/RunStatus.kt
deleted file mode 100644
index 0a7dea2..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/RunStatus.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.io
-
-/** Possible status of a flicker ru */
-enum class RunStatus(val prefix: String, val isFailure: Boolean) {
-    UNDEFINED("UNDEFINED", false),
-    RUN_EXECUTED("EXECUTED", false),
-    ASSERTION_SUCCESS("PASS", false),
-    RUN_FAILED("FAILED_RUN", true),
-    PARSING_FAILURE("FAILED_PARSING", true),
-    ASSERTION_FAILED("FAIL", true);
-
-    companion object {
-        fun fromFileName(fileName: String): RunStatus =
-            when (fileName.takeWhile { it != '_' }) {
-                RUN_EXECUTED.prefix -> RUN_EXECUTED
-                ASSERTION_SUCCESS.prefix -> ASSERTION_SUCCESS
-                RUN_FAILED.prefix -> RUN_FAILED
-                PARSING_FAILURE.prefix -> PARSING_FAILURE
-                ASSERTION_FAILED.prefix -> ASSERTION_FAILED
-                else -> UNDEFINED
-            }
-
-        val ALL: List<RunStatus> =
-            listOf(
-                UNDEFINED,
-                RUN_EXECUTED,
-                ASSERTION_SUCCESS,
-                RUN_FAILED,
-                PARSING_FAILURE,
-                ASSERTION_FAILED
-            )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/TraceType.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/TraceType.kt
deleted file mode 100644
index 0690a2c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/TraceType.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.io
-
-/** Types of traces/dumps that cna be in a flicker result */
-enum class TraceType(val fileName: String) {
-    SF("layers_trace$WINSCOPE_EXT"),
-    WM("wm_trace$WINSCOPE_EXT"),
-    TRANSACTION("transactions_trace$WINSCOPE_EXT"),
-    TRANSITION("transition_trace$WINSCOPE_EXT"),
-    EVENT_LOG("eventlog$WINSCOPE_EXT"),
-    SCREEN_RECORDING("transition.mp4"),
-    SF_DUMP("sf_dump$WINSCOPE_EXT"),
-    WM_DUMP("wm_dump$WINSCOPE_EXT");
-
-    companion object {
-        fun fromFileName(fileName: String): TraceType {
-            return when {
-                fileName == SF.fileName -> SF
-                fileName == WM.fileName -> WM
-                fileName == TRANSACTION.fileName -> TRANSACTION
-                fileName == TRANSITION.fileName -> TRANSITION
-                fileName == SCREEN_RECORDING.fileName -> SCREEN_RECORDING
-                fileName.endsWith(SF_DUMP.fileName) -> SF_DUMP
-                fileName.endsWith(WM_DUMP.fileName) -> WM_DUMP
-                else -> error("Unknown trace type for fileName=$fileName")
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/io/TransitionTimeRange.kt b/libraries/flicker/src/com/android/server/wm/traces/common/io/TransitionTimeRange.kt
deleted file mode 100644
index d69cae9..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/io/TransitionTimeRange.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.io
-
-import com.android.server.wm.traces.common.Timestamp
-
-/** Representation of a transition interval */
-data class TransitionTimeRange(val start: Timestamp, val end: Timestamp)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/BaseLayerTraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/BaseLayerTraceEntry.kt
deleted file mode 100644
index 740b866..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/BaseLayerTraceEntry.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.traces.common.layers
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import kotlin.js.JsName
-
-/** Base class for SF trace entries */
-abstract class BaseLayerTraceEntry : ITraceEntry {
-    @JsName("elapsedTimestamp") abstract val elapsedTimestamp: Long
-    @JsName("clockTimestamp") abstract val clockTimestamp: Long?
-
-    @JsName("hwcBlob") abstract val hwcBlob: String
-    @JsName("where") abstract val where: String
-    @JsName("displays") abstract val displays: Array<Display>
-    @JsName("vSyncId") abstract val vSyncId: Long
-    @JsName("stableId")
-    val stableId: String
-        get() = this::class.simpleName ?: error("Unable to determine class")
-
-    @JsName("flattenedLayers") abstract val flattenedLayers: Array<Layer>
-    @JsName("visibleLayers")
-    val visibleLayers: Array<Layer>
-        get() = flattenedLayers.filter { it.isVisible }.toTypedArray()
-
-    // for winscope
-    @JsName("isVisible") val isVisible: Boolean = true
-    @JsName("children")
-    val children: Array<Layer>
-        get() = flattenedLayers.filter { it.isRootLayer }.toTypedArray()
-
-    @JsName("physicalDisplay")
-    val physicalDisplay: Display?
-        get() = displays.firstOrNull { !it.isVirtual }
-    @JsName("physicalDisplayBounds")
-    val physicalDisplayBounds: Rect?
-        get() = physicalDisplay?.layerStackSpace
-
-    /**
-     * @return A [Layer] matching [componentMatcher] with a non-empty active buffer, or null if no
-     * layer matches [componentMatcher] or if the matching layer's buffer is empty
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getLayerWithBuffer")
-    fun getLayerWithBuffer(componentMatcher: IComponentMatcher): Layer? {
-        return flattenedLayers.firstOrNull {
-            componentMatcher.layerMatchesAnyOf(it) && it.activeBuffer.isNotEmpty
-        }
-    }
-
-    /** @return The [Layer] with [layerId], or null if the layer is not found */
-    @JsName("getLayerById")
-    fun getLayerById(layerId: Int): Layer? = this.flattenedLayers.firstOrNull { it.id == layerId }
-
-    /**
-     * Checks if any layer matching [componentMatcher] in the screen is animating.
-     *
-     * The screen is animating when a layer is not simple rotation, of when the pip overlay layer is
-     * visible
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("isAnimating")
-    fun isAnimating(
-        prevState: BaseLayerTraceEntry?,
-        componentMatcher: IComponentMatcher? = null
-    ): Boolean {
-        val curLayers =
-            visibleLayers.filter {
-                componentMatcher == null || componentMatcher.layerMatchesAnyOf(it)
-            }
-        val currIds = visibleLayers.map { it.id }
-        val prevStateLayers =
-            prevState?.visibleLayers?.filter { currIds.contains(it.id) } ?: emptyList()
-        val layersAnimating =
-            curLayers.any { currLayer ->
-                val prevLayer = prevStateLayers.firstOrNull { it.id == currLayer.id }
-                currLayer.isAnimating(prevLayer)
-            }
-        val pipAnimating = isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
-        return layersAnimating || pipAnimating
-    }
-
-    /**
-     * Check if at least one window matching [componentMatcher] is visible.
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("isVisibleComponent")
-    fun isVisible(componentMatcher: IComponentMatcher): Boolean =
-        componentMatcher.layerMatchesAnyOf(visibleLayers)
-
-    /** @return A [LayersTrace] object containing this state as its only entry */
-    @JsName("asTrace") fun asTrace(): LayersTrace = LayersTrace(arrayOf(this))
-
-    override fun toString(): String {
-        return "${timestamp}ns"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        return other is BaseLayerTraceEntry && other.timestamp == this.timestamp
-    }
-
-    override fun hashCode(): Int {
-        var result = timestamp.hashCode()
-        result = 31 * result + hwcBlob.hashCode()
-        result = 31 * result + where.hashCode()
-        result = 31 * result + displays.contentHashCode()
-        result = 31 * result + isVisible.hashCode()
-        result = 31 * result + flattenedLayers.contentHashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt
deleted file mode 100644
index b8cc861..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Display.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.layers
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-const val BLANK_LAYER_STACK = -1
-
-/** Wrapper for DisplayProto (frameworks/native/services/surfaceflinger/layerproto/display.proto) */
-class Display
-private constructor(
-    @JsName("id") val id: ULong,
-    @JsName("name") val name: String,
-    @JsName("layerStackId") val layerStackId: Int,
-    @JsName("size") val size: Size,
-    @JsName("layerStackSpace") val layerStackSpace: Rect,
-    @JsName("transform") val transform: Transform,
-    @JsName("isVirtual") val isVirtual: Boolean
-) {
-    @JsName("isOff") val isOff = layerStackId == BLANK_LAYER_STACK
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Display) return false
-
-        if (id != other.id) return false
-        if (name != other.name) return false
-        if (layerStackId != other.layerStackId) return false
-        if (size != other.size) return false
-        if (layerStackSpace != other.layerStackSpace) return false
-        if (transform != other.transform) return false
-        if (isVirtual != other.isVirtual) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = id.toInt()
-        result = 31 * result + name.hashCode()
-        result = 31 * result + layerStackId
-        result = 31 * result + size.hashCode()
-        result = 31 * result + layerStackSpace.hashCode()
-        result = 31 * result + transform.hashCode()
-        result = 31 * result + isVirtual.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Display
-            get() = withCache {
-                Display(
-                    id = 0.toULong(),
-                    name = "EMPTY",
-                    layerStackId = BLANK_LAYER_STACK,
-                    size = Size.EMPTY,
-                    layerStackSpace = Rect.EMPTY,
-                    transform = Transform.EMPTY,
-                    isVirtual = false
-                )
-            }
-
-        @JsName("from")
-        fun from(
-            id: ULong,
-            name: String,
-            layerStackId: Int,
-            size: Size,
-            layerStackSpace: Rect,
-            transform: Transform,
-            isVirtual: Boolean
-        ): Display = withCache {
-            Display(id, name, layerStackId, size, layerStackSpace, transform, isVirtual)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/HwcCompositionType.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/HwcCompositionType.kt
deleted file mode 100644
index a90c934..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/HwcCompositionType.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.traces.common.layers
-
-enum class HwcCompositionType(val value: Int) {
-    INVALID(0),
-    CLIENT(1),
-    DEVICE(2),
-    SOLID_COLOR(3),
-    CURSOR(4),
-    SIDEBAND(5),
-    UNRECOGNIZED(-1)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/ILayerProperties.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/ILayerProperties.kt
deleted file mode 100644
index 63a7838..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/ILayerProperties.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.traces.common.layers
-
-import com.android.server.wm.traces.common.ActiveBuffer
-import com.android.server.wm.traces.common.Color
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.region.Region
-import kotlin.js.JsName
-
-/**
- * Common properties of a layer that are not related to their position in the hierarchy
- *
- * These properties are frequently stable throughout the trace and can be more efficiently cached
- * than the full layers
- */
-interface ILayerProperties {
-    val visibleRegion: Region?
-    @JsName("activeBuffer") val activeBuffer: ActiveBuffer
-    @JsName("flags") val flags: Int
-    @JsName("bounds") val bounds: RectF
-    @JsName("color") val color: Color
-    @JsName("isOpaque") val isOpaque: Boolean
-    @JsName("shadowRadius") val shadowRadius: Float
-    @JsName("cornerRadius") val cornerRadius: Float
-    @JsName("type") val type: String
-    @JsName("screenBounds") val screenBounds: RectF
-    @JsName("transform") val transform: Transform
-    @JsName("sourceBounds") val sourceBounds: RectF
-    @JsName("effectiveScalingMode") val effectiveScalingMode: Int
-    @JsName("bufferTransform") val bufferTransform: Transform
-    @JsName("hwcCompositionType") val hwcCompositionType: HwcCompositionType
-    @JsName("hwcCrop") val hwcCrop: RectF
-    @JsName("hwcFrame") val hwcFrame: Rect
-    @JsName("backgroundBlurRadius") val backgroundBlurRadius: Int
-    @JsName("crop") val crop: Rect
-    @JsName("isRelativeOf") val isRelativeOf: Boolean
-    @JsName("zOrderRelativeOfId") val zOrderRelativeOfId: Int
-    @JsName("stackId") val stackId: Int
-    @JsName("requestedTransform") val requestedTransform: Transform
-    @JsName("requestedColor") val requestedColor: Color
-    @JsName("cornerRadiusCrop") val cornerRadiusCrop: RectF
-    @JsName("inputTransform") val inputTransform: Transform
-    @JsName("inputRegion") val inputRegion: Region?
-    @JsName("excludesCompositionState") val excludesCompositionState: Boolean
-
-    @JsName("isScaling")
-    val isScaling: Boolean
-        get() = transform.isScaling
-    @JsName("isTranslating")
-    val isTranslating: Boolean
-        get() = transform.isTranslating
-    @JsName("isRotating")
-    val isRotating: Boolean
-        get() = transform.isRotating
-
-    /**
-     * Checks if the layer's active buffer is empty
-     *
-     * An active buffer is empty if it is not in the proto or if its height or width are 0
-     *
-     * @return
-     */
-    @JsName("isActiveBufferEmpty")
-    val isActiveBufferEmpty: Boolean
-        get() = activeBuffer.isEmpty
-
-    /** Layer state flags as defined in LayerState.h */
-    enum class Flag(val value: Int) {
-        HIDDEN(0x01),
-        OPAQUE(0x02),
-        SKIP_SCREENSHOT(0x40),
-        SECURE(0x80),
-        ENABLE_BACKPRESSURE(0x100),
-        DISPLAY_DECORATION(0x200),
-        IGNORE_DESTINATION_FRAME(0x400)
-    }
-
-    /**
-     * Converts flags to human readable tokens.
-     *
-     * @return
-     */
-    @JsName("verboseFlags")
-    val verboseFlags: String
-        get() {
-            val tokens = Flag.values().filter { (it.value and flags) != 0 }.map { it.name }
-
-            return if (tokens.isEmpty()) {
-                ""
-            } else {
-                "${tokens.joinToString("|")} (0x${flags.toString(16)})"
-            }
-        }
-
-    /**
-     * Checks if the [Layer] has a color
-     *
-     * @return
-     */
-    @JsName("fillsColor")
-    val fillsColor: Boolean
-        get() = color.isNotEmpty
-
-    /**
-     * Checks if the [Layer] draws a shadow
-     *
-     * @return
-     */
-    @JsName("drawsShadows")
-    val drawsShadows: Boolean
-        get() = shadowRadius > 0
-
-    /**
-     * Checks if the [Layer] has blur
-     *
-     * @return
-     */
-    @JsName("hasBlur")
-    val hasBlur: Boolean
-        get() = backgroundBlurRadius > 0
-
-    /**
-     * Checks if the [Layer] has rounded corners
-     *
-     * @return
-     */
-    @JsName("hasRoundedCorners")
-    val hasRoundedCorners: Boolean
-        get() = cornerRadius > 0
-
-    /**
-     * Checks if the [Layer] draws has effects, which include:
-     * - is a color layer
-     * - is an effects layers which [fillsColor] or [drawsShadows]
-     *
-     * @return
-     */
-    @JsName("hasEffects")
-    val hasEffects: Boolean
-        get() {
-            return fillsColor || drawsShadows
-        }
-
-    fun isAnimating(prevLayerState: ILayerProperties?): Boolean =
-        when (prevLayerState) {
-            // when there's no previous state, use a heuristic based on the transform
-            null -> !transform.isSimpleRotation
-            else ->
-                visibleRegion != prevLayerState.visibleRegion ||
-                    transform != prevLayerState.transform ||
-                    color != prevLayerState.color
-        }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt
deleted file mode 100644
index 71f5d24..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Layer.kt
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.layers
-
-import com.android.server.wm.traces.common.ActiveBuffer
-import com.android.server.wm.traces.common.Color
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.region.Region
-import kotlin.js.JsName
-
-/**
- * Represents a single layer with links to its parent and child layers.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class Layer
-private constructor(
-    val name: String,
-    val id: Int,
-    val parentId: Int,
-    val z: Int,
-    val currFrame: Long,
-    properties: ILayerProperties,
-) : ILayerProperties by properties {
-    val stableId: String = "$type $id $name"
-    var parent: Layer? = null
-    var zOrderRelativeOf: Layer? = null
-    var zOrderRelativeParentOf: Int = 0
-
-    /**
-     * Checks if the [Layer] is a root layer in the hierarchy
-     *
-     * @return
-     */
-    val isRootLayer: Boolean
-        get() = parent == null
-
-    private val _children = mutableListOf<Layer>()
-    private val _occludedBy = mutableListOf<Layer>()
-    private val _partiallyOccludedBy = mutableListOf<Layer>()
-    private val _coveredBy = mutableListOf<Layer>()
-    val children: Array<Layer>
-        get() = _children.toTypedArray()
-    val occludedBy: Array<Layer>
-        get() = _occludedBy.toTypedArray()
-    val partiallyOccludedBy: Array<Layer>
-        get() = _partiallyOccludedBy.toTypedArray()
-    val coveredBy: Array<Layer>
-        get() = _coveredBy.toTypedArray()
-    var isMissing: Boolean = false
-        internal set
-
-    /**
-     * Checks if the layer is hidden, that is, if its flags contain Flag.HIDDEN
-     *
-     * @return
-     */
-    val isHiddenByPolicy: Boolean
-        get() {
-            return (flags and ILayerProperties.Flag.HIDDEN.value) != 0x0 ||
-                // offscreen layer root has a unique layer id
-                id == 0x7FFFFFFD
-        }
-
-    /**
-     * Checks if the layer is visible.
-     *
-     * A layer is visible if:
-     * - it has an active buffer or has effects
-     * - is not hidden
-     * - is not transparent
-     * - not occluded by other layers
-     *
-     * @return
-     */
-    val isVisible: Boolean
-        get() {
-            val visibleRegion =
-                if (excludesCompositionState) {
-                    // Doesn't include state sent during composition like visible region and
-                    // composition type, so we fallback on the bounds as the visible region
-                    Region.from(this.bounds)
-                } else {
-                    this.visibleRegion ?: Region.EMPTY
-                }
-            return when {
-                isHiddenByParent -> false
-                isHiddenByPolicy -> false
-                isActiveBufferEmpty && !hasEffects -> false
-                occludedBy.isNotEmpty() -> false
-                else -> visibleRegion.isNotEmpty
-            }
-        }
-
-    /**
-     * Checks if the [Layer] is hidden by its parent
-     *
-     * @return
-     */
-    val isHiddenByParent: Boolean
-        get() =
-            !isRootLayer && (parent?.isHiddenByPolicy == true || parent?.isHiddenByParent == true)
-
-    /**
-     * Gets a description of why the layer is (in)visible
-     *
-     * @return
-     */
-    val visibilityReason: Array<String>
-        get() {
-            if (isVisible) {
-                return emptyArray()
-            }
-            val reasons = mutableListOf<String>()
-            if (isHiddenByPolicy) reasons.add("Flag is hidden")
-            if (isHiddenByParent) reasons.add("Hidden by parent ${parent?.name}")
-            if (isActiveBufferEmpty) reasons.add("Buffer is empty")
-            if (color.a == 0.0f) reasons.add("Alpha is 0")
-            if (bounds.isEmpty) reasons.add("Bounds is 0x0")
-            if (bounds.isEmpty && crop.isEmpty) reasons.add("Crop is 0x0")
-            if (!transform.isValid) reasons.add("Transform is invalid")
-            if (isRelativeOf && zOrderRelativeOf == null) {
-                reasons.add("RelativeOf layer has been removed")
-            }
-            if (isActiveBufferEmpty && !fillsColor && !drawsShadows && !hasBlur) {
-                reasons.add("does not have color fill, shadow or blur")
-            }
-            if (_occludedBy.isNotEmpty()) {
-                val occludedByLayers = _occludedBy.joinToString(", ") { "${it.name} (${it.id})" }
-                reasons.add("Layer is occluded by: $occludedByLayers")
-            }
-            if (visibleRegion?.isEmpty == true) {
-                reasons.add("Visible region calculated by Composition Engine is empty")
-            }
-            if (reasons.isEmpty()) reasons.add("Unknown")
-            return reasons.toTypedArray()
-        }
-
-    val zOrderPath: Array<Int>
-        get() {
-            val zOrderRelativeOf = zOrderRelativeOf
-            val zOrderPath =
-                when {
-                    zOrderRelativeOf != null -> zOrderRelativeOf.zOrderPath.toMutableList()
-                    parent != null -> parent?.zOrderPath?.toMutableList() ?: mutableListOf()
-                    else -> mutableListOf()
-                }
-            zOrderPath.add(z)
-            return zOrderPath.toTypedArray()
-        }
-
-    /**
-     * Returns true iff the [innerLayer] screen bounds are inside or equal to this layer's
-     * [screenBounds] and neither layers are rotating.
-     */
-    fun contains(innerLayer: Layer, crop: RectF = RectF.EMPTY): Boolean {
-        return if (!this.transform.isSimpleRotation || !innerLayer.transform.isSimpleRotation) {
-            false
-        } else {
-            val thisBounds: RectF
-            val innerLayerBounds: RectF
-            if (crop.isNotEmpty) {
-                thisBounds = this.screenBounds.crop(crop)
-                innerLayerBounds = innerLayer.screenBounds.crop(crop)
-            } else {
-                thisBounds = this.screenBounds
-                innerLayerBounds = innerLayer.screenBounds
-            }
-            thisBounds.contains(innerLayerBounds)
-        }
-    }
-
-    fun addChild(childLayer: Layer) {
-        _children.add(childLayer)
-    }
-
-    fun addOccludedBy(layers: Array<Layer>) {
-        _occludedBy.addAll(layers)
-    }
-
-    fun addPartiallyOccludedBy(layers: Array<Layer>) {
-        _partiallyOccludedBy.addAll(layers)
-    }
-
-    fun addCoveredBy(layers: Array<Layer>) {
-        _coveredBy.addAll(layers)
-    }
-
-    fun overlaps(other: Layer, crop: RectF = RectF.EMPTY): Boolean {
-        val thisBounds: RectF
-        val otherBounds: RectF
-        if (crop.isNotEmpty) {
-            thisBounds = this.screenBounds.crop(crop)
-            otherBounds = other.screenBounds.crop(crop)
-        } else {
-            thisBounds = this.screenBounds
-            otherBounds = other.screenBounds
-        }
-        return !thisBounds.intersection(otherBounds).isEmpty
-    }
-
-    override fun toString(): String {
-        return buildString {
-            append(name)
-
-            if (activeBuffer.isNotEmpty) {
-                append(" buffer:$activeBuffer")
-                append(" frame#$currFrame")
-            }
-
-            if (isVisible) {
-                append(" visible:$visibleRegion")
-            }
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Layer) return false
-
-        if (name != other.name) return false
-        if (id != other.id) return false
-        if (parentId != other.parentId) return false
-        if (z != other.z) return false
-        if (currFrame != other.currFrame) return false
-        if (stableId != other.stableId) return false
-        if (zOrderRelativeOf != other.zOrderRelativeOf) return false
-        if (zOrderRelativeParentOf != other.zOrderRelativeParentOf) return false
-        if (_occludedBy != other._occludedBy) return false
-        if (_partiallyOccludedBy != other._partiallyOccludedBy) return false
-        if (_coveredBy != other._coveredBy) return false
-        if (isMissing != other.isMissing) return false
-        if (visibleRegion != other.visibleRegion) return false
-        if (activeBuffer != other.activeBuffer) return false
-        if (flags != other.flags) return false
-        if (bounds != other.bounds) return false
-        if (color != other.color) return false
-        if (shadowRadius != other.shadowRadius) return false
-        if (cornerRadius != other.cornerRadius) return false
-        if (type != other.type) return false
-        if (transform != other.transform) return false
-        if (sourceBounds != other.sourceBounds) return false
-        if (effectiveScalingMode != other.effectiveScalingMode) return false
-        if (bufferTransform != other.bufferTransform) return false
-        if (hwcCompositionType != other.hwcCompositionType) return false
-        if (hwcCrop != other.hwcCrop) return false
-        if (hwcFrame != other.hwcFrame) return false
-        if (backgroundBlurRadius != other.backgroundBlurRadius) return false
-        if (crop != other.crop) return false
-        if (isRelativeOf != other.isRelativeOf) return false
-        if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false
-        if (stackId != other.stackId) return false
-        if (requestedTransform != other.requestedTransform) return false
-        if (requestedColor != other.requestedColor) return false
-        if (cornerRadiusCrop != other.cornerRadiusCrop) return false
-        if (inputTransform != other.inputTransform) return false
-        if (inputRegion != other.inputRegion) return false
-        if (screenBounds != other.screenBounds) return false
-        if (isOpaque != other.isOpaque) return false
-        if (excludesCompositionState != other.excludesCompositionState) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = visibleRegion?.hashCode() ?: 0
-        result = 31 * result + activeBuffer.hashCode()
-        result = 31 * result + flags
-        result = 31 * result + bounds.hashCode()
-        result = 31 * result + color.hashCode()
-        result = 31 * result + shadowRadius.hashCode()
-        result = 31 * result + cornerRadius.hashCode()
-        result = 31 * result + type.hashCode()
-        result = 31 * result + transform.hashCode()
-        result = 31 * result + sourceBounds.hashCode()
-        result = 31 * result + effectiveScalingMode
-        result = 31 * result + bufferTransform.hashCode()
-        result = 31 * result + hwcCompositionType.hashCode()
-        result = 31 * result + hwcCrop.hashCode()
-        result = 31 * result + hwcFrame.hashCode()
-        result = 31 * result + backgroundBlurRadius
-        result = 31 * result + crop.hashCode()
-        result = 31 * result + isRelativeOf.hashCode()
-        result = 31 * result + zOrderRelativeOfId
-        result = 31 * result + stackId
-        result = 31 * result + requestedTransform.hashCode()
-        result = 31 * result + requestedColor.hashCode()
-        result = 31 * result + cornerRadiusCrop.hashCode()
-        result = 31 * result + inputTransform.hashCode()
-        result = 31 * result + (inputRegion?.hashCode() ?: 0)
-        result = 31 * result + screenBounds.hashCode()
-        result = 31 * result + isOpaque.hashCode()
-        result = 31 * result + name.hashCode()
-        result = 31 * result + id
-        result = 31 * result + parentId
-        result = 31 * result + z
-        result = 31 * result + currFrame.hashCode()
-        result = 31 * result + stableId.hashCode()
-        result = 31 * result + (zOrderRelativeOf?.hashCode() ?: 0)
-        result = 31 * result + zOrderRelativeParentOf
-        result = 31 * result + _children.hashCode()
-        result = 31 * result + _occludedBy.hashCode()
-        result = 31 * result + _partiallyOccludedBy.hashCode()
-        result = 31 * result + _coveredBy.hashCode()
-        result = 31 * result + isMissing.hashCode()
-        result = 31 * result + excludesCompositionState.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("from")
-        fun from(
-            name: String,
-            id: Int,
-            parentId: Int,
-            z: Int,
-            visibleRegion: Region,
-            activeBuffer: ActiveBuffer,
-            flags: Int,
-            bounds: RectF,
-            color: Color,
-            isOpaque: Boolean,
-            shadowRadius: Float,
-            cornerRadius: Float,
-            type: String,
-            screenBounds: RectF,
-            transform: Transform,
-            sourceBounds: RectF,
-            currFrame: Long,
-            effectiveScalingMode: Int,
-            bufferTransform: Transform,
-            hwcCompositionType: HwcCompositionType,
-            hwcCrop: RectF,
-            hwcFrame: Rect,
-            backgroundBlurRadius: Int,
-            crop: Rect?,
-            isRelativeOf: Boolean,
-            zOrderRelativeOfId: Int,
-            stackId: Int,
-            requestedTransform: Transform,
-            requestedColor: Color,
-            cornerRadiusCrop: RectF,
-            inputTransform: Transform,
-            inputRegion: Region?,
-            excludesCompositionState: Boolean
-        ): Layer {
-            val properties =
-                LayerProperties.from(
-                    visibleRegion,
-                    activeBuffer,
-                    flags,
-                    bounds,
-                    color,
-                    isOpaque,
-                    shadowRadius,
-                    cornerRadius,
-                    type,
-                    screenBounds,
-                    transform,
-                    sourceBounds,
-                    effectiveScalingMode,
-                    bufferTransform,
-                    hwcCompositionType,
-                    hwcCrop,
-                    hwcFrame,
-                    backgroundBlurRadius,
-                    crop,
-                    isRelativeOf,
-                    zOrderRelativeOfId,
-                    stackId,
-                    requestedTransform,
-                    requestedColor,
-                    cornerRadiusCrop,
-                    inputTransform,
-                    inputRegion,
-                    excludesCompositionState
-                )
-            return Layer(name, id, parentId, z, currFrame, properties)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerProperties.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerProperties.kt
deleted file mode 100644
index 4e1b1da..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerProperties.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * 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.traces.common.layers
-
-import com.android.server.wm.traces.common.ActiveBuffer
-import com.android.server.wm.traces.common.Color
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/** {@inheritDoc} */
-class LayerProperties
-private constructor(
-    override val visibleRegion: Region = Region.EMPTY,
-    override val activeBuffer: ActiveBuffer = ActiveBuffer.EMPTY,
-    override val flags: Int = 0,
-    override val bounds: RectF = RectF.EMPTY,
-    override val color: Color = Color.EMPTY,
-    private val _isOpaque: Boolean = false,
-    override val shadowRadius: Float = 0f,
-    override val cornerRadius: Float = 0f,
-    override val type: String = "",
-    override val screenBounds: RectF = RectF.EMPTY,
-    override val transform: Transform = Transform.EMPTY,
-    override val sourceBounds: RectF = RectF.EMPTY,
-    override val effectiveScalingMode: Int = 0,
-    override val bufferTransform: Transform = Transform.EMPTY,
-    override val hwcCompositionType: HwcCompositionType = HwcCompositionType.INVALID,
-    override val hwcCrop: RectF = RectF.EMPTY,
-    override val hwcFrame: Rect = Rect.EMPTY,
-    override val backgroundBlurRadius: Int = 0,
-    override val crop: Rect = Rect.EMPTY,
-    override val isRelativeOf: Boolean = false,
-    override val zOrderRelativeOfId: Int = 0,
-    override val stackId: Int = 0,
-    override val requestedTransform: Transform = Transform.EMPTY,
-    override val requestedColor: Color = Color.EMPTY,
-    override val cornerRadiusCrop: RectF = RectF.EMPTY,
-    override val inputTransform: Transform = Transform.EMPTY,
-    override val inputRegion: Region? = null,
-    override val excludesCompositionState: Boolean = false
-) : ILayerProperties {
-    override val isOpaque: Boolean = if (color.a != 1.0f) false else _isOpaque
-
-    override fun hashCode(): Int {
-        var result = visibleRegion.hashCode()
-        result = 31 * result + activeBuffer.hashCode()
-        result = 31 * result + flags
-        result = 31 * result + bounds.hashCode()
-        result = 31 * result + color.hashCode()
-        result = 31 * result + _isOpaque.hashCode()
-        result = 31 * result + shadowRadius.hashCode()
-        result = 31 * result + cornerRadius.hashCode()
-        result = 31 * result + type.hashCode()
-        result = 31 * result + screenBounds.hashCode()
-        result = 31 * result + transform.hashCode()
-        result = 31 * result + sourceBounds.hashCode()
-        result = 31 * result + effectiveScalingMode
-        result = 31 * result + bufferTransform.hashCode()
-        result = 31 * result + hwcCompositionType.hashCode()
-        result = 31 * result + hwcCrop.hashCode()
-        result = 31 * result + hwcFrame.hashCode()
-        result = 31 * result + backgroundBlurRadius
-        result = 31 * result + crop.hashCode()
-        result = 31 * result + isRelativeOf.hashCode()
-        result = 31 * result + zOrderRelativeOfId
-        result = 31 * result + stackId
-        result = 31 * result + requestedTransform.hashCode()
-        result = 31 * result + requestedColor.hashCode()
-        result = 31 * result + cornerRadiusCrop.hashCode()
-        result = 31 * result + inputTransform.hashCode()
-        result = 31 * result + (inputRegion?.hashCode() ?: 0)
-        result = 31 * result + screenBounds.hashCode()
-        result = 31 * result + isOpaque.hashCode()
-        result = 31 * result + excludesCompositionState.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "LayerProperties(visibleRegion=$visibleRegion, activeBuffer=$activeBuffer, " +
-            "flags=$flags, bounds=$bounds, color=$color, _isOpaque=$_isOpaque, " +
-            "shadowRadius=$shadowRadius, cornerRadius=$cornerRadius, type='$type', " +
-            "screenBounds=$screenBounds, transform=$transform, sourceBounds=$sourceBounds, " +
-            "effectiveScalingMode=$effectiveScalingMode, bufferTransform=$bufferTransform, " +
-            "hwcCompositionType=$hwcCompositionType, hwcCrop=$hwcCrop, hwcFrame=$hwcFrame, " +
-            "backgroundBlurRadius=$backgroundBlurRadius, crop=$crop, isRelativeOf=$isRelativeOf, " +
-            "zOrderRelativeOfId=$zOrderRelativeOfId, stackId=$stackId, " +
-            "requestedTransform=$requestedTransform, requestedColor=$requestedColor, " +
-            "cornerRadiusCrop=$cornerRadiusCrop, inputTransform=$inputTransform, " +
-            "inputRegion=$inputRegion, screenBounds=$screenBounds, isOpaque=$isOpaque, " +
-            "excludesCompositionState=$excludesCompositionState)"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is LayerProperties) return false
-
-        if (visibleRegion != other.visibleRegion) return false
-        if (activeBuffer != other.activeBuffer) return false
-        if (flags != other.flags) return false
-        if (bounds != other.bounds) return false
-        if (color != other.color) return false
-        if (_isOpaque != other._isOpaque) return false
-        if (shadowRadius != other.shadowRadius) return false
-        if (cornerRadius != other.cornerRadius) return false
-        if (type != other.type) return false
-        if (screenBounds != other.screenBounds) return false
-        if (transform != other.transform) return false
-        if (sourceBounds != other.sourceBounds) return false
-        if (effectiveScalingMode != other.effectiveScalingMode) return false
-        if (bufferTransform != other.bufferTransform) return false
-        if (hwcCompositionType != other.hwcCompositionType) return false
-        if (hwcCrop != other.hwcCrop) return false
-        if (hwcFrame != other.hwcFrame) return false
-        if (backgroundBlurRadius != other.backgroundBlurRadius) return false
-        if (crop != other.crop) return false
-        if (isRelativeOf != other.isRelativeOf) return false
-        if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false
-        if (stackId != other.stackId) return false
-        if (requestedTransform != other.requestedTransform) return false
-        if (requestedColor != other.requestedColor) return false
-        if (cornerRadiusCrop != other.cornerRadiusCrop) return false
-        if (inputTransform != other.inputTransform) return false
-        if (inputRegion != other.inputRegion) return false
-        if (screenBounds != other.screenBounds) return false
-        if (isOpaque != other.isOpaque) return false
-        if (excludesCompositionState != other.excludesCompositionState) return false
-
-        return true
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: LayerProperties
-            get() = withCache { LayerProperties() }
-
-        @JsName("from")
-        fun from(
-            visibleRegion: Region,
-            activeBuffer: ActiveBuffer,
-            flags: Int,
-            bounds: RectF,
-            color: Color,
-            isOpaque: Boolean,
-            shadowRadius: Float,
-            cornerRadius: Float,
-            type: String,
-            screenBounds: RectF,
-            transform: Transform,
-            sourceBounds: RectF,
-            effectiveScalingMode: Int,
-            bufferTransform: Transform,
-            hwcCompositionType: HwcCompositionType,
-            hwcCrop: RectF,
-            hwcFrame: Rect,
-            backgroundBlurRadius: Int,
-            crop: Rect?,
-            isRelativeOf: Boolean,
-            zOrderRelativeOfId: Int,
-            stackId: Int,
-            requestedTransform: Transform,
-            requestedColor: Color,
-            cornerRadiusCrop: RectF,
-            inputTransform: Transform,
-            inputRegion: Region?,
-            excludesCompositionState: Boolean
-        ): ILayerProperties {
-            return withCache {
-                LayerProperties(
-                    visibleRegion,
-                    activeBuffer,
-                    flags,
-                    bounds,
-                    color,
-                    isOpaque,
-                    shadowRadius,
-                    cornerRadius,
-                    type,
-                    screenBounds,
-                    transform,
-                    sourceBounds,
-                    effectiveScalingMode,
-                    bufferTransform,
-                    hwcCompositionType,
-                    hwcCrop,
-                    hwcFrame,
-                    backgroundBlurRadius,
-                    crop ?: Rect.EMPTY,
-                    isRelativeOf,
-                    zOrderRelativeOfId,
-                    stackId,
-                    requestedTransform,
-                    requestedColor,
-                    cornerRadiusCrop,
-                    inputTransform,
-                    inputRegion,
-                    excludesCompositionState
-                )
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt
deleted file mode 100644
index 69021d4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntry.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.layers
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.RectF
-
-/**
- * Represents a single Layer trace entry.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class LayerTraceEntry(
-    override val elapsedTimestamp: Long,
-    override val clockTimestamp: Long?,
-    override val hwcBlob: String,
-    override val where: String,
-    override val displays: Array<Display>,
-    override val vSyncId: Long,
-    _rootLayers: Array<Layer>,
-) : BaseLayerTraceEntry() {
-    override val timestamp =
-        CrossPlatform.timestamp.from(
-            systemUptimeNanos = elapsedTimestamp,
-            unixNanos = clockTimestamp
-        )
-    override val flattenedLayers: Array<Layer> = fillFlattenedLayers(_rootLayers)
-
-    private fun fillFlattenedLayers(rootLayers: Array<Layer>): Array<Layer> {
-        val layers = mutableListOf<Layer>()
-        val roots = rootLayers.fillOcclusionState().toMutableList()
-        while (roots.isNotEmpty()) {
-            val layer = roots.removeAt(0)
-            layers.add(layer)
-            roots.addAll(layer.children)
-        }
-        return layers.toTypedArray()
-    }
-
-    private fun Array<Layer>.topDownTraversal(): List<Layer> {
-        return this.sortedBy { it.z }.flatMap { it.topDownTraversal() }
-    }
-
-    private fun Layer.topDownTraversal(): List<Layer> {
-        val traverseList = mutableListOf(this)
-
-        this.children
-            .sortedBy { it.z }
-            .forEach { childLayer -> traverseList.addAll(childLayer.topDownTraversal()) }
-
-        return traverseList
-    }
-
-    private fun Array<Layer>.fillOcclusionState(): Array<Layer> {
-        val traversalList = topDownTraversal().reversed()
-
-        val opaqueLayers = mutableListOf<Layer>()
-        val transparentLayers = mutableListOf<Layer>()
-
-        traversalList.forEach { layer ->
-            val visible = layer.isVisible
-            val displaySize =
-                displays
-                    .firstOrNull { it.layerStackId == layer.stackId }
-                    ?.layerStackSpace
-                    ?.toRectF()
-                    ?: RectF.EMPTY
-
-            if (visible) {
-                val occludedBy =
-                    opaqueLayers
-                        .filter {
-                            it.stackId == layer.stackId &&
-                                it.contains(layer, displaySize) &&
-                                (!it.hasRoundedCorners || (layer.cornerRadius == it.cornerRadius))
-                        }
-                        .toTypedArray()
-                layer.addOccludedBy(occludedBy)
-                val partiallyOccludedBy =
-                    opaqueLayers
-                        .filter {
-                            it.stackId == layer.stackId &&
-                                it.overlaps(layer, displaySize) &&
-                                it !in layer.occludedBy
-                        }
-                        .toTypedArray()
-                layer.addPartiallyOccludedBy(partiallyOccludedBy)
-                val coveredBy =
-                    transparentLayers
-                        .filter { it.stackId == layer.stackId && it.overlaps(layer, displaySize) }
-                        .toTypedArray()
-                layer.addCoveredBy(coveredBy)
-
-                if (layer.isOpaque) {
-                    opaqueLayers.add(layer)
-                } else {
-                    transparentLayers.add(layer)
-                }
-            }
-        }
-
-        return this
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt
deleted file mode 100644
index 6ae243e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayerTraceEntryBuilder.kt
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.layers
-
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-/** Builder for LayerTraceEntries */
-class LayerTraceEntryBuilder {
-    private var elapsedTimestamp: Long = 0L
-    private var realTimestamp: Long? = null
-    private var orphanLayerCallback: ((Layer) -> Boolean)? = null
-    private val orphans = mutableListOf<Layer>()
-    private var layers: MutableMap<Int, Layer> = mutableMapOf()
-    private var ignoreVirtualDisplay = false
-    private var ignoreLayersStackMatchNoDisplay = false
-    private var timestamp: Timestamp? = null
-    private var displays: Array<Display> = emptyArray()
-    private var vSyncId: Long = 0L
-    private var hwcBlob: String = ""
-    private var where: String = ""
-    private var duplicateLayerCallback: ((Layer) -> Boolean) = {
-        error("Duplicate layer id found: ${it.id}")
-    }
-
-    @JsName("setVSyncId")
-    fun setVSyncId(vSyncId: String): LayerTraceEntryBuilder =
-    // Necessary for compatibility with JS number type
-    apply {
-        this.vSyncId = vSyncId.toLong()
-    }
-
-    @JsName("setHwcBlob")
-    fun setHwcBlob(hwcBlob: String): LayerTraceEntryBuilder = apply { this.hwcBlob = hwcBlob }
-
-    @JsName("setWhere")
-    fun setWhere(where: String): LayerTraceEntryBuilder = apply { this.where = where }
-
-    @JsName("setDisplays")
-    fun setDisplays(displays: Array<Display>): LayerTraceEntryBuilder = apply {
-        this.displays = displays
-    }
-
-    @JsName("setElapsedTimestamp")
-    fun setElapsedTimestamp(timestamp: String): LayerTraceEntryBuilder =
-    // Necessary for compatibility with JS number type
-    apply {
-        this.elapsedTimestamp = timestamp.toLong()
-    }
-
-    @JsName("setRealToElapsedTimeOffsetNs")
-    fun setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNs: String?): LayerTraceEntryBuilder =
-        apply {
-            this.realTimestamp =
-                if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs.toLong() != 0L) {
-                    realToElapsedTimeOffsetNs.toLong() + elapsedTimestamp
-                } else {
-                    null
-                }
-        }
-
-    @JsName("setLayers")
-    fun setLayers(layers: Array<Layer>): LayerTraceEntryBuilder = apply {
-        val result = mutableMapOf<Int, Layer>()
-        layers.forEach { layer ->
-            val id = layer.id
-            if (result.containsKey(id)) {
-                duplicateLayerCallback(layer)
-            }
-            result[id] = layer
-        }
-
-        this.layers = result
-    }
-
-    @JsName("setOrphanLayerCallback")
-    fun setOrphanLayerCallback(value: ((Layer) -> Boolean)?): LayerTraceEntryBuilder = apply {
-        this.orphanLayerCallback = value
-    }
-
-    @JsName("setDuplicateLayerCallback")
-    fun setDuplicateLayerCallback(value: ((Layer) -> Boolean)): LayerTraceEntryBuilder = apply {
-        this.duplicateLayerCallback = value
-    }
-
-    private fun notifyOrphansLayers() {
-        val callback = this.orphanLayerCallback ?: return
-
-        // Fail if we find orphan layers.
-        orphans.forEach { orphan ->
-            // Workaround for b/141326137, ignore the existence of an orphan layer
-            if (callback.invoke(orphan)) {
-                return@forEach
-            }
-            throw RuntimeException(
-                ("Failed to parse layers trace. Found orphan layer with id = ${orphan.id}" +
-                    " with parentId = ${orphan.parentId}")
-            )
-        }
-    }
-
-    /**
-     * Update the parent layers or each trace
-     *
-     * @return root layer
-     */
-    private fun updateParents() {
-        for (layer in layers.values) {
-            val parentId = layer.parentId
-
-            val parentLayer = layers[parentId]
-            if (parentLayer == null) {
-                orphans.add(layer)
-                continue
-            }
-            parentLayer.addChild(layer)
-            layer.parent = parentLayer
-        }
-    }
-
-    /**
-     * Update the parent layers or each trace
-     *
-     * @return root layer
-     */
-    private fun updateRelZParents() {
-        for (layer in layers.values) {
-            val parentId = layer.zOrderRelativeOfId
-
-            val parentLayer = layers[parentId]
-            if (parentLayer == null) {
-                layer.zOrderRelativeParentOf = parentId
-                continue
-            }
-            layer.zOrderRelativeOf = parentLayer
-        }
-    }
-
-    private fun computeRootLayers(): List<Layer> {
-        updateParents()
-        updateRelZParents()
-
-        // Find all root layers (any sibling of the root layer is considered a root layer in the
-        // trace)
-        val rootLayers = mutableListOf<Layer>()
-
-        // Getting the first orphan works because when dumping the layers, the root layer comes
-        // first, and given that orphans are added in the same order as the layers are provided
-        // in the first orphan layer should be the root layer.
-        if (orphans.isNotEmpty()) {
-            val firstRoot = orphans.first()
-            orphans.remove(firstRoot)
-            rootLayers.add(firstRoot)
-
-            val remainingRoots = orphans.filter { it.parentId == firstRoot.parentId }
-            rootLayers.addAll(remainingRoots)
-
-            // Remove RootLayers from orphans
-            orphans.removeAll(rootLayers)
-        }
-
-        return rootLayers
-    }
-
-    private fun filterOutLayersInVirtualDisplays(roots: List<Layer>): List<Layer> {
-        val physicalDisplays = displays.filterNot { it.isVirtual }.map { it.layerStackId }
-
-        return roots.filter { physicalDisplays.contains(it.stackId) }
-    }
-
-    private fun filterOutVirtualDisplays(displays: List<Display>): List<Display> {
-        return displays.filterNot { it.isVirtual }
-    }
-
-    private fun filterOutOffDisplays(displays: List<Display>): List<Display> {
-        return displays.filterNot { it.isOff }
-    }
-
-    private fun filterOutLayersStackMatchNoDisplay(roots: List<Layer>): List<Layer> {
-        val displayStacks = displays.map { it.layerStackId }
-        return roots.filter { displayStacks.contains(it.stackId) }
-    }
-
-    /**
-     * Defines if virtual displays and the layers belonging to virtual displays (e.g., Screen
-     * Recording) should be ignored while parsing the entry
-     *
-     * @param ignore If the layers from virtual displays should be ignored or not
-     */
-    @JsName("ignoreVirtualDisplay")
-    fun ignoreVirtualDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
-        this.ignoreVirtualDisplay = ignore
-    }
-
-    /**
-     * Ignore layers whose stack ID doesn't match any display. This is the case, for example, when
-     * the device screen is off, or for layers that have not yet been removed after a display change
-     * (e.g., virtual screen recording display removed)
-     *
-     * @param ignore If the layers not matching any stack id should be removed or not
-     */
-    @JsName("ignoreLayersStackMatchNoDisplay")
-    fun ignoreLayersStackMatchNoDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
-        this.ignoreLayersStackMatchNoDisplay = ignore
-    }
-
-    /** Constructs the layer hierarchy from a flattened list of layers. */
-    @JsName("build")
-    fun build(): LayerTraceEntry {
-        val allRoots = computeRootLayers()
-        var filteredRoots = allRoots
-        var filteredDisplays = displays.toList()
-
-        if (ignoreLayersStackMatchNoDisplay) {
-            filteredRoots = filterOutLayersStackMatchNoDisplay(filteredRoots)
-        }
-
-        if (ignoreVirtualDisplay) {
-            filteredRoots = filterOutLayersInVirtualDisplays(filteredRoots)
-            filteredDisplays = filterOutVirtualDisplays(filteredDisplays)
-        }
-
-        filteredDisplays = filterOutOffDisplays(filteredDisplays)
-
-        // Fail if we find orphan layers.
-        notifyOrphansLayers()
-
-        return LayerTraceEntry(
-            elapsedTimestamp,
-            realTimestamp,
-            hwcBlob,
-            where,
-            filteredDisplays.toTypedArray(),
-            vSyncId,
-            filteredRoots.toTypedArray(),
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt
deleted file mode 100644
index 9b209cc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/LayersTrace.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.layers
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-/**
- * Contains a collection of parsed Layers trace entries and assertions to apply over a single entry.
- *
- * Each entry is parsed into a list of [LayerTraceEntry] objects.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-data class LayersTrace(override val entries: Array<BaseLayerTraceEntry>) :
-    ITrace<BaseLayerTraceEntry>, List<BaseLayerTraceEntry> by entries.toList() {
-    constructor(entry: BaseLayerTraceEntry) : this(arrayOf(entry))
-
-    override fun toString(): String {
-        return "LayersTrace(Start: ${entries.firstOrNull()}, " + "End: ${entries.lastOrNull()})"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is LayersTrace) return false
-
-        if (!entries.contentEquals(other.entries)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        return entries.contentHashCode()
-    }
-
-    @JsName("vSyncSlice")
-    fun vSyncSlice(from: Int, to: Int): LayersTrace {
-        return LayersTrace(
-            this.entries
-                .dropWhile { it.vSyncId < from }
-                .dropLastWhile { it.vSyncId > to }
-                .toTypedArray()
-        )
-    }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): LayersTrace {
-        return LayersTrace(
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt b/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt
deleted file mode 100644
index 7faeb5f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/layers/Transform.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.layers
-
-import com.android.server.wm.traces.common.Matrix33
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Wrapper for TransformProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
- *
- * This class is used by flicker and Winscope
- */
-class Transform
-private constructor(@JsName("type") val type: Int?, @JsName("matrix") val matrix: Matrix33) {
-
-    /**
-     * Returns true if the applying the transform on an an axis aligned rectangle results in another
-     * axis aligned rectangle.
-     */
-    @JsName("isSimpleRotation")
-    val isSimpleRotation: Boolean = !(type?.isFlagSet(ROT_INVALID_VAL) ?: true)
-
-    /**
-     * The transformation matrix is defined as the product of: | cos(a) -sin(a) | \/ | X 0 | |
-     * sin(a) cos(a) | /\ | 0 Y |
-     *
-     * where a is a rotation angle, and X and Y are scaling factors. A transformation matrix is
-     * invalid when either X or Y is zero, as a rotation matrix is valid for any angle. When either
-     * X or Y is 0, then the scaling matrix is not invertible, which makes the transformation matrix
-     * not invertible as well. A 2D matrix with components | A B | is not invertible if and only if
-     * AD - BC = 0.
-     * ```
-     *            | C D |
-     * ```
-     * This check is included above.
-     */
-    @JsName("isValid")
-    val isValid: Boolean
-        get() {
-            // determinant of transform
-            return matrix.dsdx * matrix.dtdy != matrix.dtdx * matrix.dsdy
-        }
-
-    @JsName("isScaling")
-    val isScaling: Boolean
-        get() = type?.isFlagSet(SCALE_VAL) ?: false
-    @JsName("isTranslating")
-    val isTranslating: Boolean
-        get() = type?.isFlagSet(TRANSLATE_VAL) ?: false
-    @JsName("isRotating")
-    val isRotating: Boolean
-        get() = type?.isFlagSet(ROTATE_VAL) ?: false
-
-    @JsName("getRotation")
-    fun getRotation(): PlatformConsts.Rotation {
-        if (type == null) {
-            return PlatformConsts.Rotation.ROTATION_0
-        }
-
-        return when {
-            type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL) ->
-                PlatformConsts.Rotation.ROTATION_0
-            type.isFlagSet(ROT_90_VAL) -> PlatformConsts.Rotation.ROTATION_90
-            type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> PlatformConsts.Rotation.ROTATION_180
-            type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) ->
-                PlatformConsts.Rotation.ROTATION_270
-            else -> PlatformConsts.Rotation.ROTATION_0
-        }
-    }
-
-    @JsName("typeFlags")
-    private val typeFlags: Array<String>
-        get() {
-            if (type == null) {
-                return arrayOf("IDENTITY")
-            }
-
-            val result = mutableListOf<String>()
-
-            if (type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL)) {
-                result.add("IDENTITY")
-            }
-
-            if (type.isFlagSet(SCALE_VAL)) {
-                result.add("SCALE")
-            }
-
-            if (type.isFlagSet(TRANSLATE_VAL)) {
-                result.add("TRANSLATE")
-            }
-
-            when {
-                type.isFlagSet(ROT_INVALID_VAL) -> result.add("ROT_INVALID")
-                type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_270")
-                type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_180")
-                else -> {
-                    if (type.isFlagSet(ROT_90_VAL)) {
-                        result.add("ROT_90")
-                    }
-                    if (type.isFlagSet(FLIP_V_VAL)) {
-                        result.add("FLIP_V")
-                    }
-                    if (type.isFlagSet(FLIP_H_VAL)) {
-                        result.add("FLIP_H")
-                    }
-                }
-            }
-
-            if (result.isEmpty()) {
-                throw RuntimeException("Unknown transform type $type")
-            }
-
-            return result.toTypedArray()
-        }
-
-    @JsName("prettyPrint")
-    fun prettyPrint(): String {
-        val transformType = typeFlags.joinToString("|")
-
-        if (isSimpleTransform(type)) {
-            return transformType
-        }
-
-        return "$transformType ${matrix.prettyPrint()}"
-    }
-
-    @JsName("getTypeAsString")
-    fun getTypeAsString(): String {
-        return typeFlags.joinToString("|")
-    }
-
-    override fun toString(): String = prettyPrint()
-
-    @JsName("apply")
-    fun apply(bounds: RectF?): RectF {
-        return multiplyRect(matrix, bounds ?: RectF.EMPTY)
-    }
-
-    private data class Vec2(val x: Float, val y: Float)
-
-    @JsName("multiplyRect")
-    private fun multiplyRect(matrix: Matrix33, rect: RectF): RectF {
-        //          |dsdx dsdy  tx|         | left, top         |
-        // matrix = |dtdx dtdy  ty|  rect = |                   |
-        //          |0    0     1 |         |     right, bottom |
-
-        val leftTop = multiplyVec2(matrix, rect.left, rect.top)
-        val rightTop = multiplyVec2(matrix, rect.right, rect.top)
-        val leftBottom = multiplyVec2(matrix, rect.left, rect.bottom)
-        val rightBottom = multiplyVec2(matrix, rect.right, rect.bottom)
-
-        return RectF.from(
-            left = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f,
-            top = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f,
-            right = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f,
-            bottom = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f
-        )
-    }
-
-    @JsName("multiplyVec2")
-    private fun multiplyVec2(matrix: Matrix33, x: Float, y: Float): Vec2 {
-        // |dsdx dsdy  tx|     | x |
-        // |dtdx dtdy  ty|  x  | y |
-        // |0    0     1 |     | 1 |
-        return Vec2(
-            matrix.dsdx * x + matrix.dsdy * y + matrix.tx,
-            matrix.dtdx * x + matrix.dtdy * y + matrix.ty
-        )
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Transform) return false
-
-        if (type != other.type) return false
-        if (matrix != other.matrix) return false
-        if (isSimpleRotation != other.isSimpleRotation) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = type ?: 0
-        result = 31 * result + matrix.hashCode()
-        result = 31 * result + isSimpleRotation.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Transform
-            get() = withCache { Transform(type = null, matrix = Matrix33.EMPTY) }
-        /* transform type flags */
-        @JsName("TRANSLATE_VAL") const val TRANSLATE_VAL = 0x0001
-        @JsName("ROTATE_VAL") const val ROTATE_VAL = 0x0002
-        @JsName("SCALE_VAL") const val SCALE_VAL = 0x0004
-
-        /* orientation flags */
-        @JsName("FLIP_H_VAL") const val FLIP_H_VAL = 0x0100 // (1 << 0 << 8)
-        @JsName("FLIP_V_VAL") const val FLIP_V_VAL = 0x0200 // (1 << 1 << 8)
-        @JsName("ROT_90_VAL") const val ROT_90_VAL = 0x0400 // (1 << 2 << 8)
-        @JsName("ROT_INVALID_VAL") const val ROT_INVALID_VAL = 0x8000 // (0x80 << 8)
-
-        @JsName("isSimpleTransform")
-        fun isSimpleTransform(type: Int?): Boolean {
-            return type?.isFlagClear(ROT_INVALID_VAL or SCALE_VAL) ?: false
-        }
-
-        @JsName("isFlagClear")
-        fun Int.isFlagClear(bits: Int): Boolean {
-            return this and bits == 0
-        }
-
-        @JsName("isFlagSet")
-        fun Int.isFlagSet(bits: Int): Boolean {
-            return this and bits == bits
-        }
-
-        @JsName("from")
-        fun from(type: Int?, matrix: Matrix33): Transform = withCache { Transform(type, matrix) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/parser/AbstractParser.kt b/libraries/flicker/src/com/android/server/wm/traces/common/parser/AbstractParser.kt
deleted file mode 100644
index 0bb1ec3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/parser/AbstractParser.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.traces.common.parser
-
-import com.android.server.wm.traces.common.Cache
-import kotlin.js.JsName
-
-/** Base parser class */
-abstract class AbstractParser<InputTypeTrace, OutputTypeTrace> {
-    protected abstract val traceName: String
-    protected abstract fun doDecodeByteArray(bytes: ByteArray): InputTypeTrace
-    protected abstract fun doParse(input: InputTypeTrace): OutputTypeTrace
-
-    /**
-     * Uses a [ByteArray] to generates a trace
-     *
-     * @param bytes Parsed proto data
-     * @param clearCache If the caching used while parsing the object should be cleared
-     */
-    @JsName("parse")
-    open fun parse(bytes: ByteArray, clearCache: Boolean = true): OutputTypeTrace {
-        val input = decodeByteArray(bytes)
-        return parse(input, clearCache)
-    }
-
-    /**
-     * Uses [InputTypeTrace] to generates a trace
-     *
-     * @param input Parsed proto data
-     * @param clearCache If the caching used while parsing the object should be cleared
-     */
-    open fun parse(input: InputTypeTrace, clearCache: Boolean): OutputTypeTrace {
-        return try {
-            doParse(input)
-        } finally {
-            if (clearCache) {
-                Cache.clear()
-            }
-        }
-    }
-
-    protected fun decodeByteArray(input: ByteArray): InputTypeTrace {
-        return doDecodeByteArray(input)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/parser/events/EventLogParser.kt b/libraries/flicker/src/com/android/server/wm/traces/common/parser/events/EventLogParser.kt
deleted file mode 100644
index 41e5040..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/parser/events/EventLogParser.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.traces.common.parser.events
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.CujEvent
-import com.android.server.wm.traces.common.events.Event
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.events.EventLog.Companion.MAGIC_NUMBER
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.parser.AbstractParser
-
-operator fun <T> List<T>.component6(): T = get(5)
-
-class EventLogParser : AbstractParser<Array<String>, EventLog>() {
-    override val traceName: String = "Event Log"
-
-    override fun doDecodeByteArray(bytes: ByteArray): Array<String> {
-        val logsString = bytes.decodeToString()
-        return logsString
-            .split("\n")
-            .dropWhile {
-                it.contains(MAGIC_NUMBER) || it.contains("beginning of events") || it.isBlank()
-            }
-            .dropLastWhile { it.isBlank() }
-            .toTypedArray()
-    }
-
-    override fun doParse(input: Array<String>): EventLog {
-        val events =
-            input.map { log ->
-                val (metaData, eventData) = log.split(":", limit = 2).map { it.trim() }
-                val (rawTimestamp, uid, pid, tid, priority, tag) = metaData.split("\\s+".toRegex())
-
-                val timestamp =
-                    CrossPlatform.timestamp.from(unixNanos = rawTimestamp.replace(".", "").toLong())
-                parseEvent(timestamp, pid.toInt(), uid, tid.toInt(), tag, eventData)
-            }
-
-        return EventLog(events.toTypedArray())
-    }
-
-    private fun parseEvent(
-        timestamp: Timestamp,
-        pid: Int,
-        uid: String,
-        tid: Int,
-        tag: String,
-        eventData: String
-    ): Event {
-        eventData.split(",")
-
-        return when (tag) {
-            INPUT_FOCUS_TAG -> {
-                FocusEvent(timestamp, pid, uid, tid, parseDataArray(eventData))
-            }
-            JANK_CUJ_BEGIN_TAG -> {
-                CujEvent(timestamp, pid, uid, tid, tag, eventData)
-            }
-            JANK_CUJ_END_TAG -> {
-                CujEvent(timestamp, pid, uid, tid, tag, eventData)
-            }
-            JANK_CUJ_CANCEL_TAG -> {
-                CujEvent(timestamp, pid, uid, tid, tag, eventData)
-            }
-            else -> {
-                Event(timestamp, pid, uid, tid, tag)
-            }
-        }
-    }
-
-    private fun parseDataArray(data: String): Array<String> {
-        require(data.first() == '[')
-        require(data.last() == ']')
-        return data.drop(1).dropLast(1).split(",").toTypedArray()
-    }
-
-    fun parse(bytes: ByteArray, from: Timestamp, to: Timestamp): EventLog {
-        require(from.unixNanos < to.unixNanos) { "'to' needs to be greater than 'from'" }
-        require(from.hasUnixTimestamp && to.hasUnixTimestamp) { "Missing required timestamp type" }
-        return doParse(
-            this.doDecodeByteArray(bytes)
-                .dropWhile { getTimestampFromRawEntry(it).unixNanos < from.unixNanos }
-                .dropLastWhile { getTimestampFromRawEntry(it).unixNanos > to.unixNanos }
-                .toTypedArray()
-        )
-    }
-
-    private fun getTimestampFromRawEntry(entry: String): Timestamp {
-        val (metaData, _) = entry.split(":", limit = 2).map { it.trim() }
-        val (rawTimestamp, _, _, _, _, _) = metaData.split("\\s+".toRegex())
-        return CrossPlatform.timestamp.from(unixNanos = rawTimestamp.replace(".", "").toLong())
-    }
-
-    companion object {
-        const val EVENT_LOG_INPUT_FOCUS_TAG = 62001
-
-        const val WM_JANK_CUJ_EVENTS_BEGIN_REQUEST = 37001
-        const val WM_JANK_CUJ_EVENTS_END_REQUEST = 37002
-        const val WM_JANK_CUJ_EVENTS_CANCEL_REQUEST = 37003
-
-        const val INPUT_FOCUS_TAG = "input_focus"
-        const val JANK_CUJ_BEGIN_TAG = "jank_cuj_events_begin_request"
-        const val JANK_CUJ_END_TAG = "jank_cuj_events_end_request"
-        const val JANK_CUJ_CANCEL_TAG = "jank_cuj_events_cancel_request"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/region/Region.kt b/libraries/flicker/src/com/android/server/wm/traces/common/region/Region.kt
deleted file mode 100644
index 8ab848a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/region/Region.kt
+++ /dev/null
@@ -1,1238 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.region
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.region.Region.Companion.from
-import kotlin.js.JsName
-import kotlin.math.min
-
-/**
- * Wrapper for RegionProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
- *
- * Implementation based android.graphics.Region's native implementation found in SkRegion.cpp
- *
- * This class is used by flicker and Winscope
- *
- * It has a single constructor and different [from] functions on its companion because JS doesn't
- * support constructor overload
- */
-class Region(rects: Array<Rect> = arrayOf()) {
-    @JsName("fBounds") private var fBounds = Rect.EMPTY
-    @JsName("fRunHead") private var fRunHead: RunHead? = RunHead(isEmptyHead = true)
-
-    init {
-        if (rects.isEmpty()) {
-            setEmpty()
-        } else {
-            for (rect in rects) {
-                union(rect)
-            }
-        }
-    }
-
-    @JsName("rects")
-    val rects
-        get() = getRectsFromString(toString())
-
-    @JsName("width")
-    val width: Int
-        get() = bounds.width
-    @JsName("height")
-    val height: Int
-        get() = bounds.height
-
-    // if null we are a rect not empty
-    @JsName("isEmpty")
-    val isEmpty: Boolean
-        get() = fRunHead?.isEmptyHead ?: false
-    @JsName("isNotEmpty")
-    val isNotEmpty: Boolean
-        get() = !isEmpty
-    @JsName("bounds")
-    val bounds
-        get() = fBounds
-
-    /** Set the region to the empty region */
-    @JsName("setEmpty")
-    fun setEmpty(): Boolean {
-        fBounds = Rect.EMPTY
-        fRunHead = RunHead(isEmptyHead = true)
-
-        return false
-    }
-
-    /** Set the region to the specified region. */
-    @JsName("setRegion")
-    fun set(region: Region): Boolean {
-        fBounds = region.fBounds.clone()
-        fRunHead = region.fRunHead?.clone()
-        return !(fRunHead?.isEmptyHead ?: false)
-    }
-
-    /** Set the region to the specified rectangle */
-    @JsName("set")
-    fun set(r: Rect): Boolean {
-        return if (
-            r.isEmpty ||
-                SkRegion_kRunTypeSentinel == r.right ||
-                SkRegion_kRunTypeSentinel == r.bottom
-        ) {
-            this.setEmpty()
-        } else {
-            fBounds = r
-            fRunHead = null
-            true
-        }
-    }
-
-    /** Set the region to the specified rectangle */
-    operator fun set(left: Int, top: Int, right: Int, bottom: Int): Boolean {
-        return set(Rect(left, top, right, bottom))
-    }
-
-    @JsName("isRect")
-    fun isRect(): Boolean {
-        return fRunHead == null
-    }
-
-    @JsName("isComplex")
-    fun isComplex(): Boolean {
-        return !this.isEmpty && !this.isRect()
-    }
-
-    @JsName("contains")
-    fun contains(x: Int, y: Int): Boolean {
-        if (!fBounds.contains(x, y)) {
-            return false
-        }
-        if (this.isRect()) {
-            return true
-        }
-        require(this.isComplex())
-
-        val runs = fRunHead!!.findScanline(y)
-
-        // Skip the Bottom and IntervalCount
-        var runsIndex = 2
-
-        // Just walk this scanline, checking each interval. The X-sentinel will
-        // appear as a left-interval (runs[0]) and should abort the search.
-        //
-        // We could do a bsearch, using interval-count (runs[1]), but need to time
-        // when that would be worthwhile.
-        //
-        while (true) {
-            if (x < runs[runsIndex]) {
-                break
-            }
-            if (x < runs[runsIndex + 1]) {
-                return true
-            }
-            runsIndex += 2
-        }
-        return false
-    }
-
-    override fun toString(): String = prettyPrint()
-
-    class Iterator(@JsName("rgn") private val rgn: Region) {
-        @JsName("_done") private var done: Boolean
-        @JsName("_rect") private var rect: Rect
-        @JsName("fRuns") private var fRuns: Array<Int>? = null
-        @JsName("fRunsIndex") private var fRunsIndex = 0
-
-        init {
-            fRunsIndex = 0
-            if (rgn.isEmpty) {
-                rect = Rect.EMPTY
-                done = true
-            } else {
-                done = false
-                if (rgn.isRect()) {
-                    rect = rgn.fBounds
-                    fRuns = null
-                } else {
-                    fRuns = rgn.fRunHead!!.readonlyRuns
-                    rect = Rect(fRuns!![3], fRuns!![0], fRuns!![4], fRuns!![1])
-                    fRunsIndex = 5
-                }
-            }
-        }
-
-        @JsName("next")
-        fun next() {
-            if (done) {
-                return
-            }
-
-            if (fRuns == null) { // rect case
-                done = true
-                return
-            }
-
-            val runs = fRuns!!
-            var runsIndex = fRunsIndex
-
-            if (runs[runsIndex] < SkRegion_kRunTypeSentinel) { // valid X value
-                rect = Rect(runs[runsIndex], rect.top, runs[runsIndex + 1], rect.bottom)
-                runsIndex += 2
-            } else { // we're at the end of a line
-                runsIndex += 1
-                if (runs[runsIndex] < SkRegion_kRunTypeSentinel) { // valid Y value
-                    val intervals = runs[runsIndex + 1]
-                    if (0 == intervals) { // empty line
-                        rect = Rect(rect.left, runs[runsIndex], rect.right, rect.bottom)
-                        runsIndex += 3
-                    } else {
-                        rect = Rect(rect.left, rect.bottom, rect.right, rect.bottom)
-                    }
-
-                    assert_sentinel(runs[runsIndex + 2], false)
-                    assert_sentinel(runs[runsIndex + 3], false)
-                    rect = Rect(runs[runsIndex + 2], rect.top, runs[runsIndex + 3], runs[runsIndex])
-                    runsIndex += 4
-                } else { // end of rgn
-                    done = true
-                }
-            }
-            fRunsIndex = runsIndex
-        }
-
-        @JsName("done")
-        fun done(): Boolean {
-            return done
-        }
-
-        @JsName("rect")
-        fun rect(): Rect {
-            return rect
-        }
-    }
-
-    @JsName("prettyPrint")
-    fun prettyPrint(): String {
-        val iter = Iterator(this)
-        val result = StringBuilder("SkRegion(")
-        while (!iter.done()) {
-            val r = iter.rect()
-            result.append("(${r.left},${r.top},${r.right},${r.bottom})")
-            iter.next()
-        }
-        result.append(")")
-        return result.toString()
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Region) return false
-        if (!rects.contentEquals(other.rects)) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 1
-        val iter = Iterator(this)
-        while (!iter.done()) {
-            result = 31 * result + iter.rect().hashCode()
-            iter.next()
-        }
-        return result
-    }
-
-    // the native values for these must match up with the enum in SkRegion.h
-    enum class Op(val nativeInt: Int) {
-        DIFFERENCE(0),
-        INTERSECT(1),
-        UNION(2),
-        XOR(3),
-        REVERSE_DIFFERENCE(4),
-        REPLACE(5)
-    }
-
-    @JsName("union")
-    fun union(r: Rect): Boolean {
-        return op(r, Op.UNION)
-    }
-
-    @JsName("toRectF")
-    fun toRectF(): RectF {
-        return bounds.toRectF()
-    }
-
-    @JsName("oper")
-    private fun oper(rgnA: Region, rgnB: Region, op: Op): Boolean {
-        // simple cases
-        when (op) {
-            Op.REPLACE -> {
-                this.set(rgnB)
-                return !rgnB.isEmpty
-            }
-            Op.REVERSE_DIFFERENCE -> {
-                // collapse difference and reverse-difference into just difference
-                return this.oper(rgnB, rgnA, Op.DIFFERENCE)
-            }
-            Op.DIFFERENCE -> {
-                if (rgnA.isEmpty) {
-                    this.setEmpty()
-                    return false
-                }
-                if (rgnB.isEmpty || rgnA.bounds.intersection(rgnB.bounds).isEmpty) {
-                    this.set(rgnA)
-                    return !rgnA.isEmpty
-                }
-                if (rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds)) {
-                    this.setEmpty()
-                    return false
-                }
-            }
-            Op.INTERSECT -> {
-                when {
-                    rgnA.isEmpty ||
-                        rgnB.isEmpty ||
-                        rgnA.bounds.intersection(rgnB.bounds).isEmpty -> {
-                        this.setEmpty()
-                        return false
-                    }
-                    rgnA.isRect() && rgnB.isRect() -> {
-                        val rectIntersection = rgnA.bounds.intersection(rgnB.bounds)
-                        this.set(rgnA.bounds.intersection(rgnB.bounds))
-                        return !rectIntersection.isEmpty
-                    }
-                    rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
-                        this.set(rgnB)
-                        return !rgnB.isEmpty
-                    }
-                    rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
-                        this.set(rgnA)
-                        return !rgnA.isEmpty
-                    }
-                }
-            }
-            Op.UNION -> {
-                when {
-                    rgnA.isEmpty -> {
-                        this.set(rgnB)
-                        return !rgnB.isEmpty
-                    }
-                    rgnB.isEmpty -> {
-                        this.set(rgnA)
-                        return !rgnA.isEmpty
-                    }
-                    rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
-                        this.set(rgnA)
-                        return !rgnA.isEmpty
-                    }
-                    rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
-                        this.set(rgnB)
-                        return !rgnB.isEmpty
-                    }
-                }
-            }
-            Op.XOR -> {
-                when {
-                    rgnA.isEmpty -> {
-                        this.set(rgnB)
-                        return !rgnB.isEmpty
-                    }
-                    rgnB.isEmpty -> {
-                        this.set(rgnA)
-                        return !rgnA.isEmpty
-                    }
-                }
-            }
-        }
-
-        val array = RunArray()
-        val count = operate(rgnA.getRuns(), rgnB.getRuns(), array, op)
-        require(count <= array.count)
-        return this.setRuns(array, count)
-    }
-
-    class RunArray {
-        @JsName("kRunArrayStackCount") private val kRunArrayStackCount = 256
-        @JsName("runs") var runs: Array<Int> = Array(kRunArrayStackCount) { 0 }
-        @JsName("fCount") private var fCount: Int = kRunArrayStackCount
-
-        @JsName("count")
-        val count: Int
-            get() = fCount
-
-        operator fun get(i: Int): Int {
-            return runs[i]
-        }
-
-        @JsName("resizeToAtLeast")
-        fun resizeToAtLeast(_count: Int) {
-            var count = _count
-            if (count > fCount) {
-                // leave at least 50% extra space for future growth.
-                count += count shr 1
-                val newRuns = Array(count) { 0 }
-                runs.forEachIndexed { index, value -> newRuns[index] = value }
-                runs = newRuns
-                fCount = count
-            }
-        }
-
-        operator fun set(i: Int, value: Int) {
-            runs[i] = value
-        }
-
-        @JsName("subList")
-        fun subList(startIndex: Int, stopIndex: Int): RunArray {
-            val subRuns = RunArray()
-            subRuns.resizeToAtLeast(this.fCount)
-            for (i in startIndex until stopIndex) {
-                subRuns.runs[i - startIndex] = this.runs[i]
-            }
-            return subRuns
-        }
-
-        @JsName("clone")
-        fun clone(): RunArray {
-            val clone = RunArray()
-            clone.runs = runs.copyOf()
-            clone.fCount = fCount
-            return clone
-        }
-    }
-
-    /**
-     * Set this region to the result of performing the Op on the specified regions. Return true if
-     * the result is not empty.
-     */
-    @JsName("opRegions")
-    fun op(rgnA: Region, rgnB: Region, op: Op): Boolean {
-        return this.oper(rgnA, rgnB, op)
-    }
-
-    @JsName("getRuns")
-    private fun getRuns(): Array<Int> {
-        val runs: Array<Int>
-        if (this.isEmpty) {
-            runs = Array(kRectRegionRuns) { 0 }
-            runs[0] = SkRegion_kRunTypeSentinel
-        } else if (this.isRect()) {
-            runs = buildRectRuns(fBounds)
-        } else {
-            runs = fRunHead!!.readonlyRuns
-        }
-
-        return runs
-    }
-
-    @JsName("buildRectRuns")
-    private fun buildRectRuns(bounds: Rect): Array<Int> {
-        val runs = Array(kRectRegionRuns) { 0 }
-        runs[0] = bounds.top
-        runs[1] = bounds.bottom
-        runs[2] = 1 // 1 interval for this scanline
-        runs[3] = bounds.left
-        runs[4] = bounds.right
-        runs[5] = SkRegion_kRunTypeSentinel
-        runs[6] = SkRegion_kRunTypeSentinel
-        return runs
-    }
-
-    class RunHead(@JsName("isEmptyHead") val isEmptyHead: Boolean = false) {
-        @JsName("setRuns")
-        fun setRuns(runs: RunArray, count: Int) {
-            this.runs = runs
-            this.fRunCount = count
-        }
-
-        @JsName("computeRunBounds")
-        fun computeRunBounds(): Rect {
-            var runsIndex = 0
-            val top = runs[runsIndex]
-            runsIndex++
-
-            var bot: Int
-            var ySpanCount = 0
-            var intervalCount = 0
-            var left = Int.MAX_VALUE
-            var right = Int.MIN_VALUE
-
-            do {
-                bot = runs[runsIndex]
-                runsIndex++
-                require(bot < SkRegion_kRunTypeSentinel)
-                ySpanCount += 1
-
-                val intervals = runs[runsIndex]
-                runsIndex++
-                require(intervals >= 0)
-                require(intervals < SkRegion_kRunTypeSentinel)
-
-                if (intervals > 0) {
-                    val L = runs[runsIndex]
-                    require(L < SkRegion_kRunTypeSentinel)
-                    if (left > L) {
-                        left = L
-                    }
-
-                    runsIndex += intervals * 2
-                    val R = runs[runsIndex - 1]
-                    require(R < SkRegion_kRunTypeSentinel)
-                    if (right < R) {
-                        right = R
-                    }
-
-                    intervalCount += intervals
-                }
-                require(SkRegion_kRunTypeSentinel == runs[runsIndex])
-                runsIndex += 1 // skip x-sentinel
-
-                // test Y-sentinel
-            } while (SkRegion_kRunTypeSentinel > runs[runsIndex])
-
-            fYSpanCount = ySpanCount
-            fIntervalCount = intervalCount
-
-            return Rect.from(left, top, right, bot)
-        }
-
-        @JsName("clone")
-        fun clone(): RunHead {
-            val clone = RunHead(isEmptyHead)
-            clone.fIntervalCount = fIntervalCount
-            clone.fYSpanCount = fYSpanCount
-            clone.runs = runs.clone()
-            clone.fRunCount = fRunCount
-            return clone
-        }
-
-        /**
-         * Return the scanline that contains the Y value. This requires that the Y value is already
-         * known to be contained within the bounds of the region, and so this routine never returns
-         * nullptr.
-         *
-         * It returns the beginning of the scanline, starting with its Bottom value.
-         */
-        @JsName("findScanline")
-        fun findScanline(y: Int): Array<Int> {
-            val runs = readonlyRuns
-
-            // if the top-check fails, we didn't do a quick check on the bounds
-            require(y >= runs[0])
-
-            var runsIndex = 1 // skip top-Y
-            while (true) {
-                val bottom = runs[runsIndex]
-                // If we hit this, we've walked off the region, and our bounds check
-                // failed.
-                require(bottom < SkRegion_kRunTypeSentinel)
-                if (y < bottom) {
-                    break
-                }
-                runsIndex = skipEntireScanline(runsIndex)
-            }
-
-            val newArray = Array(runs.size - runsIndex) { 0 }
-            runs.copyInto(
-                newArray,
-                destinationOffset = 0,
-                startIndex = runsIndex,
-                endIndex = runs.size - runsIndex
-            )
-            return newArray
-        }
-
-        /**
-         * Given a scanline (including its Bottom value at runs[0]), return the next scanline.
-         * Asserts that there is one (i.e. runs[0] < Sentinel)
-         */
-        @JsName("SkipEntireScanline")
-        fun skipEntireScanline(_runsIndex: Int): Int {
-            var runsIndex = _runsIndex
-            // we are not the Y Sentinel
-            require(runs[runsIndex] < SkRegion_kRunTypeSentinel)
-
-            val intervals = runs[runsIndex + 1]
-            require(runs[runsIndex + 2 + intervals * 2] == SkRegion_kRunTypeSentinel)
-
-            // skip the entire line [B N [L R] S]
-            runsIndex += 1 + 1 + intervals * 2 + 1
-            return runsIndex
-        }
-
-        private var fIntervalCount: Int = 0
-        private var fYSpanCount: Int = 0
-        var runs = RunArray()
-        var fRunCount: Int = 0
-
-        val readonlyRuns: Array<Int>
-            get() = runs.runs
-    }
-
-    @JsName("setRuns")
-    private fun setRuns(runs: RunArray, _count: Int): Boolean {
-        require(_count > 0)
-
-        var count = _count
-
-        if (isRunCountEmpty(count)) {
-            assert_sentinel(runs[count - 1], true)
-            return this.setEmpty()
-        }
-
-        // trim off any empty spans from the top and bottom
-        // weird I should need this, perhaps op() could be smarter...
-        var trimmedRuns = runs
-        if (count > kRectRegionRuns) {
-            var stopIndex = count
-            assert_sentinel(runs[0], false) // top
-            assert_sentinel(runs[1], false) // bottom
-            // runs[2] is uncomputed intervalCount
-
-            var trimLeft = false
-            if (runs[3] == SkRegion_kRunTypeSentinel) { // should be first left...
-                trimLeft = true
-                assert_sentinel(runs[1], false) // bot: a sentinal would mean two in a row
-                assert_sentinel(runs[2], false) // intervalcount
-                assert_sentinel(runs[3], false) // left
-                assert_sentinel(runs[4], false) // right
-            }
-
-            assert_sentinel(runs[stopIndex - 1], true)
-            assert_sentinel(runs[stopIndex - 2], true)
-
-            var trimRight = false
-            // now check for a trailing empty span
-            if (runs[stopIndex - 5] == SkRegion_kRunTypeSentinel) {
-                // eek, stop[-4] was a bottom with no x-runs
-                trimRight = true
-            }
-
-            var startIndex = 0
-            if (trimLeft) {
-                startIndex += 3
-                trimmedRuns = trimmedRuns.subList(startIndex, count) // skip empty initial span
-                trimmedRuns[0] = runs[1] // set new top to prev bottom
-            }
-            if (trimRight) {
-                // kill empty last span
-                trimmedRuns[stopIndex - 4] = SkRegion_kRunTypeSentinel
-                stopIndex -= 3
-                assert_sentinel(runs[stopIndex - 1], true) // last y-sentinel
-                assert_sentinel(runs[stopIndex - 2], true) // last x-sentinel
-                assert_sentinel(runs[stopIndex - 3], false) // last right
-                assert_sentinel(runs[stopIndex - 4], false) // last left
-                assert_sentinel(runs[stopIndex - 5], false) // last interval-count
-                assert_sentinel(runs[stopIndex - 6], false) // last bottom
-                trimmedRuns = trimmedRuns.subList(startIndex, stopIndex)
-            }
-
-            count = stopIndex - startIndex
-        }
-
-        require(count >= kRectRegionRuns)
-
-        if (runsAreARect(trimmedRuns, count)) {
-            fBounds = Rect(trimmedRuns[3], trimmedRuns[0], trimmedRuns[4], trimmedRuns[1])
-            return this.setRect(fBounds)
-        }
-
-        //  if we get here, we need to become a complex region
-        if (!this.isComplex() || fRunHead!!.fRunCount != count) {
-            fRunHead = RunHead()
-            fRunHead!!.fRunCount = count
-            require(this.isComplex())
-        }
-
-        // must call this before we can write directly into runs()
-        // in case we are sharing the buffer with another region (copy on write)
-        // fRunHead = fRunHead->ensureWritable();
-        // memcpy(fRunHead, runs, count * sizeof(RunType))
-        fRunHead!!.setRuns(trimmedRuns, count)
-        fBounds = fRunHead!!.computeRunBounds()
-
-        // Our computed bounds might be too large, so we have to check here.
-        if (fBounds.isEmpty) {
-            return this.setEmpty()
-        }
-
-        return true
-    }
-
-    @JsName("setRect")
-    private fun setRect(r: Rect): Boolean {
-        if (
-            r.isEmpty ||
-                SkRegion_kRunTypeSentinel == r.right ||
-                SkRegion_kRunTypeSentinel == r.bottom
-        ) {
-            return this.setEmpty()
-        }
-        fBounds = r
-        fRunHead = null
-        return true
-    }
-
-    @JsName("isRunCountEmpty")
-    private fun isRunCountEmpty(count: Int): Boolean {
-        return count <= 2
-    }
-
-    @JsName("runsAreARect")
-    private fun runsAreARect(runs: RunArray, count: Int): Boolean {
-        require(count >= kRectRegionRuns)
-
-        if (count == kRectRegionRuns) {
-            assert_sentinel(runs[1], false) // bottom
-            require(1 == runs[2])
-            assert_sentinel(runs[3], false) // left
-            assert_sentinel(runs[4], false) // right
-            assert_sentinel(runs[5], true)
-            assert_sentinel(runs[6], true)
-
-            require(runs[0] < runs[1]) // valid height
-            require(runs[3] < runs[4]) // valid width
-
-            return true
-        }
-        return false
-    }
-
-    class RgnOper(
-        @JsName("top") var top: Int,
-        @JsName("runArray") private val runArray: RunArray,
-        op: Op
-    ) {
-        @JsName("fMin") private val fMin = gOpMinMax[op]!!.min
-        @JsName("fMax") private val fMax = gOpMinMax[op]!!.max
-
-        @JsName("fStartDst") private var fStartDst = 0
-        @JsName("fPrevDst") private var fPrevDst = 1
-        @JsName("fPrevLen") private var fPrevLen = 0
-
-        @JsName("addSpan")
-        fun addSpan(
-            bottom: Int,
-            aRuns: Array<Int>,
-            bRuns: Array<Int>,
-            aRunsIndex: Int,
-            bRunsIndex: Int
-        ) {
-            // skip X values and slots for the next Y+intervalCount
-            val start = fPrevDst + fPrevLen + 2
-            // start points to beginning of dst interval
-            val stop =
-                operateOnSpan(aRuns, bRuns, aRunsIndex, bRunsIndex, runArray, start, fMin, fMax)
-            val len = stop - start
-            require(len >= 1 && (len and 1) == 1)
-            require(SkRegion_kRunTypeSentinel == runArray[stop - 1])
-
-            // Assert memcmp won't exceed fArray->count().
-            require(runArray.count >= start + len - 1)
-            if (
-                fPrevLen == len &&
-                    (1 == len ||
-                        runArray.subList(fPrevDst, fPrevDst + len).runs ==
-                            runArray.subList(start, start + len).runs)
-            ) {
-                // update Y value
-                runArray[fPrevDst - 2] = bottom
-            } else { // accept the new span
-                if (len == 1 && fPrevLen == 0) {
-                    top = bottom // just update our bottom
-                } else {
-                    runArray[start - 2] = bottom
-                    runArray[start - 1] = len / 2 // len shr 1
-                    fPrevDst = start
-                    fPrevLen = len
-                }
-            }
-        }
-
-        @JsName("flush")
-        fun flush(): Int {
-            runArray[fStartDst] = top
-            // Previously reserved enough for TWO sentinals.
-            // SkASSERT(fArray->count() > SkToInt(fPrevDst + fPrevLen));
-            runArray[fPrevDst + fPrevLen] = SkRegion_kRunTypeSentinel
-            return fPrevDst - fStartDst + fPrevLen + 1
-        }
-
-        class SpanRect(
-            @JsName("aRuns") private val aRuns: Array<Int>,
-            @JsName("bRuns") private val bRuns: Array<Int>,
-            aIndex: Int,
-            bIndex: Int
-        ) {
-            @JsName("fLeft") var fLeft: Int = 0
-            @JsName("fRight") var fRight: Int = 0
-            @JsName("fInside") var fInside: Int = 0
-
-            @JsName("fALeft") var fALeft: Int
-            @JsName("fARight") var fARight: Int
-            @JsName("fBLeft") var fBLeft: Int
-            @JsName("fBRight") var fBRight: Int
-            @JsName("fARuns") var fARuns: Int
-            @JsName("fBRuns") var fBRuns: Int
-
-            init {
-                fALeft = aRuns[aIndex]
-                fARight = aRuns[aIndex + 1]
-                fBLeft = bRuns[bIndex]
-                fBRight = bRuns[bIndex + 1]
-                fARuns = aIndex + 2
-                fBRuns = bIndex + 2
-            }
-
-            @JsName("done")
-            fun done(): Boolean {
-                require(fALeft <= SkRegion_kRunTypeSentinel)
-                require(fBLeft <= SkRegion_kRunTypeSentinel)
-                return fALeft == SkRegion_kRunTypeSentinel && fBLeft == SkRegion_kRunTypeSentinel
-            }
-
-            @JsName("next")
-            fun next() {
-                val inside: Int
-                val left: Int
-                var right = 0
-                var aFlush = false
-                var bFlush = false
-
-                var aLeft = fALeft
-                var aRight = fARight
-                var bLeft = fBLeft
-                var bRight = fBRight
-
-                if (aLeft < bLeft) {
-                    inside = 1
-                    left = aLeft
-                    if (aRight <= bLeft) { // [...] <...>
-                        right = aRight
-                        aFlush = true
-                    } else { // [...<..]...> or [...<...>...]
-                        aLeft = bLeft
-                        right = bLeft
-                    }
-                } else if (bLeft < aLeft) {
-                    inside = 2
-                    left = bLeft
-                    if (bRight <= aLeft) { // [...] <...>
-                        right = bRight
-                        bFlush = true
-                    } else { // [...<..]...> or [...<...>...]
-                        bLeft = aLeft
-                        right = aLeft
-                    }
-                } else { // a_left == b_left
-                    inside = 3
-                    left = aLeft // or b_left
-                    if (aRight <= bRight) {
-                        bLeft = aRight
-                        right = aRight
-                        aFlush = true
-                    }
-                    if (bRight <= aRight) {
-                        aLeft = bRight
-                        right = bRight
-                        bFlush = true
-                    }
-                }
-
-                if (aFlush) {
-                    aLeft = aRuns[fARuns]
-                    fARuns++
-                    aRight = aRuns[fARuns]
-                    fARuns++
-                }
-                if (bFlush) {
-                    bLeft = bRuns[fBRuns]
-                    fBRuns++
-                    bRight = bRuns[fBRuns]
-                    fBRuns++
-                }
-
-                require(left <= right)
-
-                // now update our state
-                fALeft = aLeft
-                fARight = aRight
-                fBLeft = bLeft
-                fBRight = bRight
-
-                fLeft = left
-                fRight = right
-                fInside = inside
-            }
-        }
-
-        @JsName("operateOnSpan")
-        private fun operateOnSpan(
-            a_runs: Array<Int>,
-            b_runs: Array<Int>,
-            a_run_index: Int,
-            b_run_index: Int,
-            array: RunArray,
-            dstOffset: Int,
-            min: Int,
-            max: Int
-        ): Int {
-            // This is a worst-case for this span plus two for TWO terminating sentinels.
-            array.resizeToAtLeast(
-                dstOffset +
-                    distance_to_sentinel(a_runs, a_run_index) +
-                    distance_to_sentinel(b_runs, b_run_index) +
-                    2
-            )
-            var dstIndex = dstOffset
-
-            val rec = SpanRect(a_runs, b_runs, a_run_index, b_run_index)
-            var firstInterval = true
-
-            while (!rec.done()) {
-                rec.next()
-
-                val left = rec.fLeft
-                val right = rec.fRight
-
-                // add left,right to our dst buffer (checking for coincidence
-                if (
-                    (rec.fInside - min).toUInt() <= (max - min).toUInt() && left < right
-                ) { // skip if equal
-                    if (firstInterval || array[dstIndex - 1] < left) {
-                        array[dstIndex] = left
-                        dstIndex++
-                        array[dstIndex] = right
-                        dstIndex++
-                        firstInterval = false
-                    } else {
-                        // update the right edge
-                        array[dstIndex - 1] = right
-                    }
-                }
-            }
-
-            array[dstIndex] = SkRegion_kRunTypeSentinel
-            dstIndex++
-            return dstIndex // dst - &(*array)[0]
-        }
-
-        @JsName("distance_to_sentinel")
-        private fun distance_to_sentinel(runs: Array<Int>, startIndex: Int): Int {
-            var index = startIndex
-            if (runs.size <= index) {
-                println("We fucked up...")
-            }
-            while (runs[index] != SkRegion_kRunTypeSentinel) {
-                if (runs.size <= index + 2) {
-                    println("We fucked up...")
-                    return 256
-                }
-                index += 2
-            }
-            return index - startIndex
-        }
-    }
-
-    @JsName("operate")
-    private fun operate(
-        aRuns: Array<Int>,
-        bRuns: Array<Int>,
-        dst: RunArray,
-        op: Op,
-        _aRunsIndex: Int = 0,
-        _bRunsIndex: Int = 0
-    ): Int {
-        var aRunsIndex = _aRunsIndex
-        var bRunsIndex = _bRunsIndex
-
-        var aTop = aRuns[aRunsIndex]
-        aRunsIndex++
-        var aBot = aRuns[aRunsIndex]
-        aRunsIndex++
-        var bTop = bRuns[bRunsIndex]
-        bRunsIndex++
-        var bBot = bRuns[bRunsIndex]
-        bRunsIndex++
-
-        aRunsIndex++ // skip the intervalCount
-        bRunsIndex++ // skip the intervalCount
-
-        val gEmptyScanline: Array<Int> =
-            arrayOf(
-                0, // fake bottom value
-                0, // zero intervals
-                SkRegion_kRunTypeSentinel,
-                // just need a 2nd value, since spanRec.init() reads 2 values, even
-                // though if the first value is the sentinel, it ignores the 2nd value.
-                // w/o the 2nd value here, we might read uninitialized memory.
-                // This happens when we are using gSentinel, which is pointing at
-                // our sentinel value.
-                0
-            )
-        val gSentinel = 2
-
-        // Now aRuns and bRuns to their intervals (or sentinel)
-
-        assert_sentinel(aTop, false)
-        assert_sentinel(aBot, false)
-        assert_sentinel(bTop, false)
-        assert_sentinel(bBot, false)
-
-        val oper = RgnOper(min(aTop, bTop), dst, op)
-
-        var prevBot = SkRegion_kRunTypeSentinel // so we fail the first test
-
-        while (aBot < SkRegion_kRunTypeSentinel || bBot < SkRegion_kRunTypeSentinel) {
-            var top: Int
-            var bot = 0
-
-            var run0 = gEmptyScanline
-            var run0Index = gSentinel
-            var run1 = gEmptyScanline
-            var run1Index = gSentinel
-            var aFlush = false
-            var bFlush = false
-
-            if (aTop < bTop) {
-                top = aTop
-                run0 = aRuns
-                run0Index = aRunsIndex
-                if (aBot <= bTop) { // [...] <...>
-                    bot = aBot
-                    aFlush = true
-                } else { // [...<..]...> or [...<...>...]
-                    aTop = bTop
-                    bot = bTop
-                }
-            } else if (bTop < aTop) {
-                top = bTop
-                run1 = bRuns
-                run1Index = bRunsIndex
-                if (bBot <= aTop) { // [...] <...>
-                    bot = bBot
-                    bFlush = true
-                } else { // [...<..]...> or [...<...>...]
-                    bTop = aTop
-                    bot = aTop
-                }
-            } else { // aTop == bTop
-                top = aTop // or bTop
-                run0 = aRuns
-                run0Index = aRunsIndex
-                run1 = bRuns
-                run1Index = bRunsIndex
-                if (aBot <= bBot) {
-                    bTop = aBot
-                    bot = aBot
-                    aFlush = true
-                }
-                if (bBot <= aBot) {
-                    aTop = bBot
-                    bot = bBot
-                    bFlush = true
-                }
-            }
-
-            if (top > prevBot) {
-                oper.addSpan(top, gEmptyScanline, gEmptyScanline, gSentinel, gSentinel)
-            }
-            oper.addSpan(bot, run0, run1, run0Index, run1Index)
-
-            if (aFlush) {
-                aRunsIndex = skipIntervals(aRuns, aRunsIndex)
-                aTop = aBot
-                aBot = aRuns[aRunsIndex]
-                aRunsIndex++ // skip to next index
-                aRunsIndex++ // skip uninitialized intervalCount
-                if (aBot == SkRegion_kRunTypeSentinel) {
-                    aTop = aBot
-                }
-            }
-            if (bFlush) {
-                bRunsIndex = skipIntervals(bRuns, bRunsIndex)
-                bTop = bBot
-                bBot = bRuns[bRunsIndex]
-                bRunsIndex++ // skip to next index
-                bRunsIndex++ // skip uninitialized intervalCount
-                if (bBot == SkRegion_kRunTypeSentinel) {
-                    bTop = bBot
-                }
-            }
-
-            prevBot = bot
-        }
-
-        return oper.flush()
-    }
-
-    @JsName("skipIntervals")
-    private fun skipIntervals(runs: Array<Int>, index: Int): Int {
-        val intervals = runs[index - 1]
-        return index + intervals * 2 + 1
-    }
-
-    /**
-     * Perform the specified Op on this region and the specified region. Return true if the result
-     * of the op is not empty.
-     */
-    @JsName("opRegion")
-    fun op(region: Region, op: Op): Boolean {
-        return op(this, region, op)
-    }
-
-    /**
-     * Perform the specified Op on this region and the specified rect. Return true if the result of
-     * the op is not empty.
-     */
-    @JsName("op")
-    fun op(left: Int, top: Int, right: Int, bottom: Int, op: Op): Boolean {
-        return op(Rect(left, top, right, bottom), op)
-    }
-
-    /**
-     * Perform the specified Op on this region and the specified rect. Return true if the result of
-     * the op is not empty.
-     */
-    @JsName("opRect")
-    fun op(r: Rect, op: Op): Boolean {
-        return op(from(r), op)
-    }
-
-    /**
-     * Set this region to the result of performing the Op on the specified rect and region. Return
-     * true if the result is not empty.
-     */
-    @JsName("opAndSetRegion")
-    fun op(rect: Rect, region: Region, op: Op): Boolean {
-        return op(from(rect), region, op)
-    }
-
-    @JsName("minus")
-    fun minus(other: Region): Region {
-        val thisRegion = from(this)
-        thisRegion.op(other, Op.XOR)
-        return thisRegion
-    }
-
-    @JsName("coversAtMost")
-    fun coversAtMost(testRegion: Region): Boolean {
-        val testRect = testRegion.bounds
-        val intersection = from(this)
-        return intersection.op(testRect, Op.INTERSECT) && !intersection.op(this, Op.XOR)
-    }
-
-    @JsName("coversAtLeast")
-    fun coversAtLeast(testRegion: Region): Boolean {
-        val intersection = from(this)
-        return intersection.op(testRegion, Op.INTERSECT) && !intersection.op(testRegion, Op.XOR)
-    }
-
-    @JsName("coversMoreThan")
-    fun coversMoreThan(testRegion: Region): Boolean {
-        return coversAtLeast(testRegion) && from(this).minus(testRegion).isNotEmpty
-    }
-
-    @JsName("outOfBoundsRegion")
-    fun outOfBoundsRegion(testRegion: Region): Region {
-        val testRect = testRegion.bounds
-        val outOfBoundsRegion = from(this)
-        outOfBoundsRegion.op(testRect, Op.INTERSECT) && outOfBoundsRegion.op(this, Op.XOR)
-        return outOfBoundsRegion
-    }
-
-    @JsName("uncoveredRegion")
-    fun uncoveredRegion(testRegion: Region): Region {
-        val uncoveredRegion = from(this)
-        uncoveredRegion.op(testRegion, Op.INTERSECT) && uncoveredRegion.op(testRegion, Op.XOR)
-        return uncoveredRegion
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Region
-            get() = Region()
-
-        @JsName("SkRegion_kRunTypeSentinel") const val SkRegion_kRunTypeSentinel = 0x7FFFFFFF
-
-        @JsName("kRectRegionRuns") const val kRectRegionRuns = 7
-
-        class MinMax(val min: Int, val max: Int)
-
-        @JsName("gOpMinMax")
-        val gOpMinMax =
-            mapOf(
-                Op.DIFFERENCE to MinMax(1, 1),
-                Op.INTERSECT to MinMax(3, 3),
-                Op.UNION to MinMax(1, 3),
-                Op.XOR to MinMax(1, 2)
-            )
-
-        @JsName("from")
-        fun from(left: Int, top: Int, right: Int, bottom: Int): Region =
-            from(Rect(left, top, right, bottom))
-
-        @JsName("fromRegion") fun from(region: Region): Region = Region().also { it.set(region) }
-
-        @JsName("fromRect")
-        fun from(rect: Rect? = null): Region =
-            Region().also {
-                it.fRunHead = null
-                it.setRect(rect ?: Rect.EMPTY)
-            }
-
-        @JsName("fromRectF") fun from(rect: RectF?): Region = from(rect?.toRect())
-
-        @JsName("fromEmpty") fun from(): Region = from(Rect.EMPTY)
-
-        @JsName("SkRegionValueIsSentinel")
-        private fun SkRegionValueIsSentinel(value: Int): Boolean {
-            return value == SkRegion_kRunTypeSentinel
-        }
-
-        @JsName("assert_sentinel")
-        private fun assert_sentinel(value: Int, isSentinel: Boolean) {
-            require(SkRegionValueIsSentinel(value) == isSentinel)
-        }
-
-        @JsName("getRectsFromString")
-        private fun getRectsFromString(regionString: String): Array<Rect> {
-            val rects = mutableListOf<Rect>()
-
-            if (regionString == "SkRegion()") {
-                return rects.toTypedArray()
-            }
-
-            var nativeRegionString = regionString.replace("SkRegion", "")
-            nativeRegionString = nativeRegionString.substring(2, nativeRegionString.length - 2)
-            nativeRegionString = nativeRegionString.replace(")(", ",")
-
-            var rect = Rect.EMPTY
-            for ((i, coord) in nativeRegionString.split(",").withIndex()) {
-                when (i % 4) {
-                    0 -> rect = Rect(coord.toInt(), 0, 0, 0)
-                    1 -> rect = Rect(rect.left, coord.toInt(), 0, 0)
-                    2 -> rect = Rect(rect.left, rect.top, coord.toInt(), 0)
-                    3 -> {
-                        rect = Rect(rect.left, rect.top, rect.right, coord.toInt())
-                        rects.add(rect)
-                    }
-                }
-            }
-
-            return rects.toTypedArray()
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionEntry.kt
deleted file mode 100644
index 53f11f4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionEntry.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.region
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-/**
- * Represents a single Region trace entry.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- *
- * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
- */
-class RegionEntry(@JsName("region") val region: Region, override val timestamp: Timestamp) :
-    ITraceEntry
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionTrace.kt
deleted file mode 100644
index 41499fe..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/region/RegionTrace.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.region
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import kotlin.js.JsName
-
-/**
- * Contains a collection of parsed Region trace entries.
- *
- * Each entry is parsed into a list of [RegionEntry] objects.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-data class RegionTrace(
-    @JsName("components") val components: IComponentMatcher?,
-    override val entries: Array<RegionEntry>
-) : ITrace<RegionEntry>, List<RegionEntry> by entries.toList() {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is RegionTrace) return false
-
-        if (components != other.components) return false
-        if (!entries.contentEquals(other.entries)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = components.hashCode()
-        result = 31 * result + entries.contentHashCode()
-        return result
-    }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ITrace<RegionEntry> {
-        return RegionTrace(
-            components,
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/AssertionInvocationGroup.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/AssertionInvocationGroup.kt
deleted file mode 100644
index 36d28e0..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/AssertionInvocationGroup.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.traces.common.service
-
-enum class AssertionInvocationGroup {
-    BLOCKING,
-    NON_BLOCKING
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/IFlickerService.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/IFlickerService.kt
deleted file mode 100644
index 36fd60f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/IFlickerService.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.traces.common.service
-
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-
-interface IFlickerService {
-    fun process(reader: IReader): List<IAssertionResult>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/IScenarioInstance.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/IScenarioInstance.kt
deleted file mode 100644
index e525215..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/IScenarioInstance.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service
-
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.config.FaasScenarioType
-import com.android.server.wm.traces.common.transition.Transition
-
-interface IScenarioInstance : IScenario {
-    val type: FaasScenarioType
-    // A reader to read the part of the trace associated with the scenario instance
-    val reader: IReader
-
-    val associatedTransition: Transition?
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/ITracesCollector.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/ITracesCollector.kt
deleted file mode 100644
index 4da2bb3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/ITracesCollector.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.traces.common.service
-
-import com.android.server.wm.traces.common.io.IReader
-
-interface ITracesCollector {
-    fun start()
-    fun stop()
-    fun getResultReader(): IReader
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt
deleted file mode 100644
index 270152d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/PlatformConsts.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service
-
-import kotlin.js.JsName
-
-object PlatformConsts {
-    /**
-     * The default Display id, which is the id of the primary display assuming there is one.
-     *
-     * Duplicated from [Display.DEFAULT_DISPLAY] because this class is used by JVM and KotlinJS
-     */
-    @JsName("DEFAULT_DISPLAY") const val DEFAULT_DISPLAY = 0
-
-    /**
-     * Window type: an application window that serves as the "base" window of the overall
-     * application
-     *
-     * Duplicated from [WindowManager.LayoutParams.TYPE_BASE_APPLICATION] because this class is used
-     * by JVM and KotlinJS
-     */
-    @JsName("TYPE_BASE_APPLICATION") const val TYPE_BASE_APPLICATION = 1
-
-    /**
-     * Window type: special application window that is displayed while the application is starting
-     *
-     * Duplicated from [WindowManager.LayoutParams.TYPE_APPLICATION_STARTING] because this class is
-     * used by JVM and KotlinJS
-     */
-    @JsName("TYPE_APPLICATION_STARTING") const val TYPE_APPLICATION_STARTING = 3
-
-    /**
-     * Rotation constant: 0 degrees rotation (natural orientation)
-     *
-     * Duplicated from [Surface.ROTATION_0] because this class is used by JVM and KotlinJS
-     */
-    @JsName("ROTATION_0") const val ROTATION_0 = 0
-
-    /**
-     * Rotation constant: 90 degrees rotation.
-     *
-     * Duplicated from [Surface.ROTATION_90] because this class is used by JVM and KotlinJS
-     */
-    @JsName("ROTATION_90") const val ROTATION_90 = 1
-
-    /**
-     * Rotation constant: 180 degrees rotation.
-     *
-     * Duplicated from [Surface.ROTATION_180] because this class is used by JVM and KotlinJS
-     */
-    @JsName("ROTATION_180") const val ROTATION_180 = 2
-
-    /**
-     * Rotation constant: 270 degrees rotation.
-     *
-     * Duplicated from [Surface.ROTATION_270] because this class is used by JVM and KotlinJS
-     */
-    @JsName("ROTATION_270") const val ROTATION_270 = 3
-
-    /**
-     * Navigation bar mode constant: 3 button navigation.
-     *
-     * Duplicated from [WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY] because this
-     * class is used by JVM and KotlinJS
-     */
-    @JsName("MODE_GESTURAL")
-    const val MODE_GESTURAL = "com.android.internal.systemui.navbar.gestural"
-
-    /**
-     * Navigation bar mode : gestural navigation.
-     *
-     * Duplicated from [WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY] because this
-     * class is used by JVM and KotlinJS
-     */
-    @JsName("MODE_3BUTTON")
-    const val MODE_3BUTTON = "com.android.internal.systemui.navbar.threebutton"
-
-    enum class Rotation(val description: String, val value: Int) {
-        ROTATION_0("ROTATION_0", PlatformConsts.ROTATION_0),
-        ROTATION_90("ROTATION_90", PlatformConsts.ROTATION_90),
-        ROTATION_180("ROTATION_180", PlatformConsts.ROTATION_180),
-        ROTATION_270("ROTATION_270", PlatformConsts.ROTATION_270);
-
-        fun isRotated() = this == ROTATION_90 || this == ROTATION_270
-
-        companion object {
-            private val VALUES = values()
-            @JsName("getByValue")
-            fun getByValue(value: Int) = if (value == -1) ROTATION_0 else VALUES[value]
-        }
-    }
-
-    enum class NavBar(val description: String, val value: String) {
-        MODE_3BUTTON("3_BUTTON_NAV", PlatformConsts.MODE_3BUTTON),
-        MODE_GESTURAL("GESTURAL_NAV", PlatformConsts.MODE_GESTURAL);
-
-        companion object {
-            @JsName("getByValue")
-            fun getByValue(value: String) {
-                when (value) {
-                    PlatformConsts.MODE_3BUTTON -> MODE_3BUTTON
-                    PlatformConsts.MODE_GESTURAL -> MODE_GESTURAL
-                    else -> error("Unknown nav bar mode $value")
-                }
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/ScenarioInstance.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/ScenarioInstance.kt
deleted file mode 100644
index 1118684..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/ScenarioInstance.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.CujType
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.config.FaasScenarioType
-import com.android.server.wm.traces.common.transition.Transition
-
-data class ScenarioInstance(
-    override val type: FaasScenarioType,
-    override val startRotation: PlatformConsts.Rotation,
-    override val endRotation: PlatformConsts.Rotation,
-    val startTimestamp: Timestamp,
-    val endTimestamp: Timestamp,
-    override val reader: IReader,
-    val associatedCuj: CujType? = null,
-    override val associatedTransition: Transition? = null,
-) : IScenarioInstance {
-    val startTransaction
-        get() = associatedTransition?.startTransaction
-    val finishTransaction
-        get() = associatedTransition?.finishTransaction
-
-    // b/227752705
-    override val navBarMode
-        get() = error("Unsupported")
-
-    override val key = "${type.name}_${startRotation}_$endRotation"
-
-    override val description = key
-
-    override val isEmpty = false
-
-    override fun toString() = key
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/TagIdGenerator.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/TagIdGenerator.kt
deleted file mode 100644
index 986e08f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/TagIdGenerator.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.traces.common.service
-
-class TagIdGenerator {
-    companion object {
-        fun getNext() = ++latestId
-        private var latestId = 0
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionResult.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionResult.kt
deleted file mode 100644
index fd1f6e8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionResult.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-/** Base class for a FaaS assertion */
-data class AssertionResult(
-    override val assertion: IFaasAssertion,
-    override val assertionError: Throwable?,
-) : IAssertionResult {
-    override val failed = (assertionError !== null)
-    override val passed = !failed
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionTemplate.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionTemplate.kt
deleted file mode 100644
index 1ae334a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/AssertionTemplate.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup.NON_BLOCKING
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Base class for a FaaS assertion */
-abstract class AssertionTemplate : IAssertionTemplate {
-    override val assertionName = "${this@AssertionTemplate::class.simpleName}"
-    private var stabilityGroup: AssertionInvocationGroup = NON_BLOCKING
-
-    override fun createAssertion(scenarioInstance: IScenarioInstance): IFaasAssertion {
-        return object : IFaasAssertion {
-            override val name = this@AssertionTemplate.assertionName
-
-            override val stabilityGroup
-                get() = this@AssertionTemplate.stabilityGroup
-
-            override fun evaluate(): AssertionResult {
-                val wmTraceSubject =
-                    scenarioInstance.reader.readWmTrace()?.let { WindowManagerTraceSubject(it) }
-                val layersTraceSubject =
-                    scenarioInstance.reader.readLayersTrace()?.let { LayersTraceSubject(it) }
-
-                var assertionError: Throwable? = null
-                try {
-                    if (wmTraceSubject !== null) {
-                        doEvaluate(scenarioInstance, wmTraceSubject)
-                    }
-                    if (layersTraceSubject !== null) {
-                        doEvaluate(scenarioInstance, layersTraceSubject)
-                    }
-                    if (wmTraceSubject !== null && layersTraceSubject !== null) {
-                        doEvaluate(scenarioInstance, wmTraceSubject, layersTraceSubject)
-                    }
-                } catch (e: Throwable) {
-                    assertionError = e
-                }
-
-                return AssertionResult(this, assertionError)
-            }
-        }
-    }
-
-    /**
-     * Evaluates assertions that require only WM traces. NOTE: Will not run if WM trace is not
-     * available.
-     */
-    protected open fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        // Does nothing, unless overridden
-    }
-
-    /**
-     * Evaluates assertions that require only SF traces. NOTE: Will not run if layers trace is not
-     * available.
-     */
-    protected open fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        layerSubject: LayersTraceSubject
-    ) {
-        // Does nothing, unless overridden
-    }
-
-    /**
-     * Evaluates assertions that require both SF and WM traces. NOTE: Will not run if any of the
-     * traces are not available.
-     */
-    protected open fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject,
-        layerSubject: LayersTraceSubject
-    ) {
-        // Does nothing, unless overridden
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (other == null) {
-            return false
-        }
-        // Ensure both assertions are instances of the same class.
-        return this::class == other::class
-    }
-
-    override fun hashCode(): Int {
-        var result = stabilityGroup.hashCode()
-        result = 31 * result + assertionName.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/ComponentTemplate.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/ComponentTemplate.kt
deleted file mode 100644
index 7acce32..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/ComponentTemplate.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-
-data class ComponentTemplate(
-    val name: String,
-    val build: (scenarioInstance: IScenarioInstance) -> IComponentMatcher
-) {
-    override fun equals(other: Any?): Boolean {
-        return other is ComponentTemplate && name == other.name && build == other.build
-    }
-
-    override fun hashCode(): Int {
-        return name.hashCode() * 39 + build.hashCode()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/Components.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/Components.kt
deleted file mode 100644
index 90aa446..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/Components.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.FullComponentIdMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.transition.Transition
-
-object Components {
-    val NAV_BAR = ComponentTemplate("Navbar") { ComponentNameMatcher.NAV_BAR }
-    val STATUS_BAR = ComponentTemplate("StatusBar") { ComponentNameMatcher.STATUS_BAR }
-    val LAUNCHER = ComponentTemplate("Launcher") { ComponentNameMatcher.LAUNCHER }
-
-    val OPENING_APP =
-        ComponentTemplate("OPENING_APP") { scenarioInstance: IScenarioInstance ->
-            openingAppFrom(
-                scenarioInstance.associatedTransition ?: error("Missing associated transition")
-            )
-        }
-    val CLOSING_APP =
-        ComponentTemplate("CLOSING_APP") { scenarioInstance: IScenarioInstance ->
-            closingAppFrom(
-                scenarioInstance.associatedTransition ?: error("Missing associated transition")
-            )
-        }
-
-    val EMPTY = ComponentTemplate("") { ComponentNameMatcher("", "") }
-
-    // TODO: Extract out common code between two functions below
-    private fun openingAppFrom(transition: Transition): IComponentMatcher {
-        val targetChanges =
-            transition.changes.filter {
-                it.transitMode == Transition.Companion.Type.OPEN ||
-                    it.transitMode == Transition.Companion.Type.TO_FRONT
-            }
-
-        val openingLayerIds = targetChanges.map { it.layerId }
-        require(openingLayerIds.size == 1) {
-            "Expected 1 opening layer but got ${openingLayerIds.size}"
-        }
-
-        val openingWindowIds = targetChanges.map { it.windowId }
-        require(openingWindowIds.size == 1) {
-            "Expected 1 opening window but got ${openingWindowIds.size}"
-        }
-
-        val windowId = openingWindowIds.first()
-        val layerId = openingLayerIds.first()
-        return FullComponentIdMatcher(windowId, layerId)
-    }
-
-    private fun closingAppFrom(transition: Transition): IComponentMatcher {
-        val targetChanges =
-            transition.changes.filter {
-                it.transitMode == Transition.Companion.Type.CLOSE ||
-                    it.transitMode == Transition.Companion.Type.TO_BACK
-            }
-
-        val closingLayerIds = targetChanges.map { it.layerId }
-        require(closingLayerIds.size == 1) {
-            "Expected 1 closing layer but got ${closingLayerIds.size}"
-        }
-
-        val closingWindowIds = targetChanges.map { it.windowId }
-        require(closingWindowIds.size == 1) {
-            "Expected 1 closing window but got ${closingWindowIds.size}"
-        }
-
-        val windowId = closingWindowIds.first()
-        val layerId = closingLayerIds.first()
-        return FullComponentIdMatcher(windowId, layerId)
-    }
-
-    val byType: Map<String, ComponentTemplate> =
-        mapOf("OPENING_APP" to OPENING_APP, "CLOSING_APP" to CLOSING_APP)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/FaasData.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/FaasData.kt
deleted file mode 100644
index a97cede..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/FaasData.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.service.ScenarioInstance
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-data class FaasData(
-    val scenarioInstance: ScenarioInstance,
-    val entireWmTrace: WindowManagerTrace,
-    val entireLayersTrace: LayersTrace
-) {
-    fun toFacts(): Collection<Fact> {
-        return mutableListOf(
-                Fact("Extracted from WM trace start", entireWmTrace.first().timestamp),
-                Fact("Extracted from WM trace end", entireWmTrace.first().timestamp),
-                Fact("Extracted from SF trace start", entireLayersTrace.first().timestamp),
-                Fact("Extracted from SF trace end", entireLayersTrace.first().timestamp),
-                Fact("Scenario description", scenarioInstance.description),
-                Fact("Scenario rotation", scenarioInstance.startRotation),
-                Fact("Scenario start", "${scenarioInstance.startTimestamp}"),
-                Fact("Scenario end", "${scenarioInstance.endTimestamp}")
-            )
-            .apply {
-                if (scenarioInstance.associatedTransition != null) {
-                    this.add(
-                        Fact(
-                            "Associated transition changes",
-                            scenarioInstance.associatedTransition.changes.joinToString(
-                                "\n  -",
-                                "\n  -"
-                            ) { "${it.transitMode} ${it.layerId}" }
-                        )
-                    )
-                }
-                if (scenarioInstance.associatedCuj !== null) {
-                    this.add(Fact("Associated CUJ", scenarioInstance.associatedCuj))
-                }
-            }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionResult.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionResult.kt
deleted file mode 100644
index 5227253..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionResult.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-interface IAssertionResult {
-    val assertion: IFaasAssertion
-    val passed: Boolean
-    val failed: Boolean
-        get() = !passed
-    val assertionError: Throwable?
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionTemplate.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionTemplate.kt
deleted file mode 100644
index b7b4d61..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IAssertionTemplate.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-
-interface IAssertionTemplate {
-    val assertionName: String
-    fun createAssertion(scenarioInstance: IScenarioInstance): IFaasAssertion
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IFaasAssertion.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IFaasAssertion.kt
deleted file mode 100644
index fa640a7..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/IFaasAssertion.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors
-
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-
-interface IFaasAssertion {
-    val name: String
-    val stabilityGroup: AssertionInvocationGroup
-
-    fun evaluate(): IAssertionResult
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesInvisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesInvisible.kt
deleted file mode 100644
index 7c14ee4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesInvisible.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
- * created and/or becomes visible during the transition.
- */
-class AppLayerBecomesInvisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isVisible(component.build(scenarioInstance))
-            .then()
-            .isInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesVisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesVisible.kt
deleted file mode 100644
index 5e1339c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerBecomesVisible.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
- * created and/or becomes visible during the transition.
- */
-class AppLayerBecomesVisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isInvisible(component.build(scenarioInstance))
-            .then()
-            .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-            .then()
-            .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-            .then()
-            .isVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt
deleted file mode 100644
index 2cca03c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtEnd.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-class AppLayerCoversFullScreenAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
-        val startDisplayBounds =
-            layersTrace.last().physicalDisplayBounds ?: error("Missing physical display bounds")
-
-        layerSubject
-            .last()
-            .visibleRegion(component.build(scenarioInstance))
-            .coversExactly(startDisplayBounds)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtStart.kt
deleted file mode 100644
index be3e359..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerCoversFullScreenAtStart.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-class AppLayerCoversFullScreenAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
-        val startDisplayBounds =
-            layersTrace.first().physicalDisplayBounds ?: error("Missing physical display bounds")
-
-        layerSubject
-            .first()
-            .visibleRegion(component.build(scenarioInstance))
-            .coversExactly(startDisplayBounds)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtEnd.kt
deleted file mode 100644
index f1ffd85..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtEnd.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is invisible at the end of the transition */
-class AppLayerIsInvisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.last().isInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtStart.kt
deleted file mode 100644
index d193d70..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsInvisibleAtStart.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is invisible at the start of the transition */
-class AppLayerIsInvisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.first().isInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAlways.kt
deleted file mode 100644
index 11885c3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAlways.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is visible throughout the animation */
-class AppLayerIsVisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtEnd.kt
deleted file mode 100644
index 0cea61c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtEnd.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is visible at the end of the transition */
-class AppLayerIsVisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.last().isVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtStart.kt
deleted file mode 100644
index 34d6d64..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerIsVisibleAtStart.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is visible at the start of the transition */
-class AppLayerIsVisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.first().isVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReduces.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReduces.kt
deleted file mode 100644
index 273c9c2..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReduces.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks that the visible region of [component] always reduces during the animation */
-class AppLayerReduces(component: ComponentTemplate) : AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val layerMatcher = component.build(scenarioInstance)
-        val layerList = layerSubject.layers { layerMatcher.layerMatchesAnyOf(it) && it.isVisible }
-        layerList.zipWithNext { previous, current ->
-            current.visibleRegion.coversAtMost(previous.visibleRegion.region)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt
deleted file mode 100644
index 64554d9..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerRemainInsideDisplayBounds.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the [component] layer remains inside the display bounds throughout the whole animation
- */
-class AppLayerRemainInsideDisplayBounds(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .invoke("appLayerRemainInsideDisplayBounds") { entry ->
-                val displays = entry.entry.displays
-                if (displays.isEmpty()) {
-                    entry.fail("No displays found")
-                }
-                displays.forEach { display ->
-                    entry
-                        .visibleRegion(component.build(scenarioInstance))
-                        .coversAtMost(display.layerStackSpace)
-                }
-            }
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReplacesLauncher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReplacesLauncher.kt
deleted file mode 100644
index 96b30cd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppLayerReplacesLauncher.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Asserts that:
- * ```
- *     [Components.LAUNCHER] is visible at the start of the trace
- *     [Components.LAUNCHER] becomes invisible during the trace and (in the same entry)
- *     [component] becomes visible
- *     [component] remains visible until the end of the trace
- * ```
- */
-class AppLayerReplacesLauncher(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isVisible(ComponentNameMatcher.LAUNCHER)
-            .then()
-            .isVisible(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesInvisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesInvisible.kt
deleted file mode 100644
index 06e3435..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesInvisible.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
- * created and/or becomes visible during the transition.
- */
-class AppWindowBecomesInvisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowVisible(component.build(scenarioInstance))
-            .then()
-            .isAppWindowInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesPinned.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesPinned.kt
deleted file mode 100644
index cbe2b57..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesPinned.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks that [component] window becomes pinned */
-class AppWindowBecomesPinned(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .invoke("appWindowIsNotPinned") { it.isNotPinned(component.build(scenarioInstance)) }
-            .then()
-            .invoke("appWindowIsPinned") { it.isPinned(component.build(scenarioInstance)) }
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesTopWindow.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesTopWindow.kt
deleted file mode 100644
index c6946dd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesTopWindow.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
- * created and/or becomes visible during the transition.
- */
-class AppWindowBecomesTopWindow(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        val testApp = component.build(scenarioInstance)
-        wmSubject
-            .isAppWindowNotOnTop(testApp)
-            .then()
-            .isAppWindowOnTop(
-                testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
-            )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesVisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesVisible.kt
deleted file mode 100644
index 70feefd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowBecomesVisible.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that the app layer doesn't exist or is invisible at the start of the transition, but is
- * created and/or becomes visible during the transition.
- */
-class AppWindowBecomesVisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowInvisible(component.build(scenarioInstance))
-            .then()
-            .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-            .then()
-            .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-            .then()
-            .isAppWindowVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt
deleted file mode 100644
index e7c2ed2..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtEnd.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowCoversFullScreenAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
-        val startDisplayBounds =
-            layersTrace.last().physicalDisplayBounds ?: error("Missing physical display bounds")
-
-        wmSubject
-            .last()
-            .visibleRegion(component.build(scenarioInstance))
-            .coversExactly(startDisplayBounds)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtStart.kt
deleted file mode 100644
index 6b21e5a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowCoversFullScreenAtStart.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowCoversFullScreenAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        val layersTrace = scenarioInstance.reader.readLayersTrace() ?: error("Missing layers trace")
-        val startDisplayBounds =
-            layersTrace.first().physicalDisplayBounds ?: error("Missing physical display bounds")
-
-        wmSubject
-            .first()
-            .visibleRegion(component.build(scenarioInstance))
-            .coversExactly(startDisplayBounds)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtEnd.kt
deleted file mode 100644
index 362a704..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtEnd.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks if the [getWindowState] layer is invisible at the end of the transition */
-class AppWindowIsInvisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.last().isAppWindowInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtStart.kt
deleted file mode 100644
index 8891f06..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsInvisibleAtStart.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowIsInvisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.first().isAppWindowInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsTopWindowAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsTopWindowAtStart.kt
deleted file mode 100644
index 41412fc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsTopWindowAtStart.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowIsTopWindowAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.first().isAppWindowOnTop(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAlways.kt
deleted file mode 100644
index ff35b0a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAlways.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks that [component] window remains visible throughout the transition */
-class AppWindowIsVisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.isAppWindowVisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtEnd.kt
deleted file mode 100644
index d361a1b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtEnd.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowIsVisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.last().isAppWindowVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtStart.kt
deleted file mode 100644
index 1cac542..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowIsVisibleAtStart.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowIsVisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.first().isAppWindowVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtEnd.kt
deleted file mode 100644
index cc06014..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtEnd.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowOnTopAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.last().isAppWindowOnTop(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtStart.kt
deleted file mode 100644
index 1e54dd4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowOnTopAtStart.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-class AppWindowOnTopAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.first().isAppWindowOnTop(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt
deleted file mode 100644
index d661e08..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowRemainInsideDisplayBounds.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that [component] window remains inside the display bounds throughout the whole animation
- */
-class AppWindowRemainInsideDisplayBounds(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .invoke("appWindowRemainInsideDisplayBounds") { entry ->
-                val displays = entry.wmState.displays
-                if (displays.isEmpty()) {
-                    entry.fail("No displays found")
-                }
-                val display = entry.wmState.displays.sortedBy { it.id }.first()
-                entry
-                    .visibleRegion(component.build(scenarioInstance))
-                    .coversAtMost(display.displayRect)
-            }
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt
deleted file mode 100644
index a87b3d4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AppWindowReplacesLauncherAsTopWindow.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.service.assertors.Components
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that [Components.LAUNCHER] is the top visible app window at the start of the transition
- * and that it is replaced by [component] during the transition
- */
-class AppWindowReplacesLauncherAsTopWindow(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-            .then()
-            .isAppWindowOnTop(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AssertionTemplateWithComponent.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AssertionTemplateWithComponent.kt
deleted file mode 100644
index e92e706..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/AssertionTemplateWithComponent.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-
-/** Base class for tests that require a [component] named window name */
-abstract class AssertionTemplateWithComponent(val component: ComponentTemplate) :
-    AssertionTemplate() {
-
-    override val assertionName = "${this::class.simpleName}(${component.name})"
-
-    override fun equals(other: Any?): Boolean {
-        if (other !is AssertionTemplateWithComponent) {
-            return false
-        }
-
-        // Check both assertions are instances of the same class.
-        if (this::class != component::class) {
-            return false
-        }
-
-        // TODO: Make sure equality is properly defined on the component
-        return other.component == component
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + component.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAlways.kt
deleted file mode 100644
index e5a352b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAlways.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the stack space of all displays is fully covered by any visible layer, during the whole
- * transitions
- */
-class EntireScreenCoveredAlways : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .invoke("entireScreenCovered") { entry ->
-                val displays = entry.entry.displays
-                if (displays.isEmpty()) {
-                    entry.fail("No displays found")
-                }
-                displays.forEach { display ->
-                    entry.visibleRegion().coversAtLeast(display.layerStackSpace)
-                }
-            }
-            .forAllEntries()
-    }
-
-    override fun equals(other: Any?): Boolean {
-        return other is EntireScreenCoveredAlways
-    }
-
-    override fun hashCode(): Int {
-        return this::class.hashCode()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtEnd.kt
deleted file mode 100644
index 6a29f86..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtEnd.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the stack space of all displays is fully covered by any visible layer, at the end of
- * the transition
- */
-class EntireScreenCoveredAtEnd : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val subject = layerSubject.last()
-        val displays = subject.entry.displays
-        if (displays.isEmpty()) {
-            subject.fail("No displays found")
-        }
-        displays.forEach { display ->
-            subject.visibleRegion().coversAtLeast(display.layerStackSpace)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtStart.kt
deleted file mode 100644
index fb02470..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/EntireScreenCoveredAtStart.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the stack space of all displays is fully covered by any visible layer, at the start of
- * the transition
- */
-class EntireScreenCoveredAtStart : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val subject = layerSubject.first()
-        val displays = subject.entry.displays
-        if (displays.isEmpty()) {
-            subject.fail("No displays found")
-        }
-        displays.forEach { display ->
-            subject.visibleRegion().coversAtLeast(display.layerStackSpace)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherReplacesAppLayer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherReplacesAppLayer.kt
deleted file mode 100644
index 5d5473c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherReplacesAppLayer.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Asserts that:
- * ```
- *     [component] is visible at the start of the trace
- *     [component] becomes invisible during the trace and (in the same entry)
- *     [Components.LAUNCHER] becomes visible
- * ```
- */
-class LauncherReplacesAppLayer(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isVisible(component.build(scenarioInstance))
-            .then()
-            .isVisible(ComponentNameMatcher.LAUNCHER)
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesOutOfTop.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesOutOfTop.kt
deleted file mode 100644
index 9bcf24b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesOutOfTop.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.assertors.Components
-
-/** Checks that [Components.LAUNCHER] starts on top and moves out of top during the transition */
-class LauncherWindowMovesOutOfTop : WindowMovesOutOfTop(Components.LAUNCHER)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesToTop.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesToTop.kt
deleted file mode 100644
index d6874a0..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowMovesToTop.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.assertors.Components
-
-/** Checks that [Components.LAUNCHER] starts not on top and moves to top during the transition */
-class LauncherWindowMovesToTop : WindowMovesToTop(Components.LAUNCHER)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt
deleted file mode 100644
index 62a9400..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LauncherWindowReplacesAppAsTopWindow.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.service.assertors.Components
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that [component] is the top visible app window at the start of the transition and that it
- * is replaced by [Components.LAUNCHER] during the transition
- */
-class LauncherWindowReplacesAppAsTopWindow(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowOnTop(component.build(scenarioInstance))
-            .then()
-            .isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesInvisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesInvisible.kt
deleted file mode 100644
index 039c81e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesInvisible.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the [componentMatcher] layer is visible at the start of the transition and becomes
- * invisible
- */
-class LayerBecomesInvisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isVisible(component.build(scenarioInstance))
-            .then()
-            .isInvisible(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesVisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesVisible.kt
deleted file mode 100644
index eb8cd69..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerBecomesVisible.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the [componentMatcher] layer is invisible at the start of the transition and becomes
- * visible
- */
-class LayerBecomesVisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isInvisible(component.build(scenarioInstance))
-            .then()
-            .isVisible(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAlways.kt
deleted file mode 100644
index 694a19c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAlways.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [componentMatcher] layer is invisible during the entire transition */
-class LayerIsInvisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtEnd.kt
deleted file mode 100644
index dd3886c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtEnd.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [componentMatcher] layer is invisible at the end of the transition */
-class LayerIsInvisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.last().isInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtStart.kt
deleted file mode 100644
index 641b4a5..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsInvisibleAtStart.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [componentMatcher] layer is invisible at the start of the transition */
-class LayerIsInvisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.first().isInvisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAlways.kt
deleted file mode 100644
index 2f5c416..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAlways.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [componentMatcher] layer is visible during the entire transition */
-class LayerIsVisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.isVisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtEnd.kt
deleted file mode 100644
index 4b69c8a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtEnd.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is visible at the end of the transition */
-class LayerIsVisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.last().isVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtStart.kt
deleted file mode 100644
index b023e24..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/LayerIsVisibleAtStart.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/** Checks if the [component] layer is visible at the start of the transition */
-class LayerIsVisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.first().isVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesInvisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesInvisible.kt
deleted file mode 100644
index c76b940..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesInvisible.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that non-app window [component] is visible at the start of the transition and becomes
- * invisible
- */
-open class NonAppWindowBecomesInvisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isNonAppWindowVisible(component.build(scenarioInstance))
-            .then()
-            .isNonAppWindowInvisible(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesVisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesVisible.kt
deleted file mode 100644
index 35ba2ea..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowBecomesVisible.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that non-app window [component] is invisible at the start of the transition and becomes
- * visible
- */
-class NonAppWindowBecomesVisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isNonAppWindowInvisible(component.build(scenarioInstance))
-            .then()
-            .isAppWindowVisible(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsInvisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsInvisibleAlways.kt
deleted file mode 100644
index 9717cc3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsInvisibleAlways.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks if the [component] window is invisible during the entire transition */
-class NonAppWindowIsInvisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.isNonAppWindowInvisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAlways.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAlways.kt
deleted file mode 100644
index 788d9f4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAlways.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks if the [component] window is visible during the entire transition */
-class NonAppWindowIsVisibleAlways(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.isNonAppWindowVisible(component.build(scenarioInstance)).forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt
deleted file mode 100644
index 2447d7e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtEnd.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks if the [component] window is visible at the end of the transition */
-class NonAppWindowIsVisibleAtEnd(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.last().isNonAppWindowVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtStart.kt
deleted file mode 100644
index 95f0c4c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/NonAppWindowIsVisibleAtStart.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks if the [component] window is visible at the end of the transition */
-class NonAppWindowIsVisibleAtStart(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.first().isNonAppWindowVisible(component.build(scenarioInstance))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/PipWindowBecomesInvisible.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/PipWindowBecomesInvisible.kt
deleted file mode 100644
index 4b5d5a3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/PipWindowBecomesInvisible.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that [component] window is pinned and visible at the start and then becomes unpinned and
- * invisible at the same moment, and remains unpinned and invisible until the end of the transition
- */
-class PipWindowBecomesInvisible(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        val appComponent = component
-        wmSubject
-            .invoke("hasPipWindow") {
-                it.isPinned(appComponent.build(scenarioInstance))
-                    .isAppWindowVisible(appComponent.build(scenarioInstance))
-            }
-            .then()
-            .invoke("!hasPipWindow") {
-                it.isNotPinned(appComponent.build(scenarioInstance))
-                    .isAppWindowInvisible(appComponent.build(scenarioInstance))
-            }
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/RotationLayerAppearsAndVanishes.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/RotationLayerAppearsAndVanishes.kt
deleted file mode 100644
index bb67c09..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/RotationLayerAppearsAndVanishes.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
- * flicker, and disappears before the transition is complete.
- */
-class RotationLayerAppearsAndVanishes(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject
-            .isVisible(component.build(scenarioInstance))
-            .then()
-            .isVisible(ComponentNameMatcher.ROTATION)
-            .then()
-            .isVisible(component.build(scenarioInstance))
-            .isInvisible(ComponentNameMatcher.ROTATION)
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtEnd.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtEnd.kt
deleted file mode 100644
index 08fd693..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtEnd.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the [ComponentNameMatcher.STATUS_BAR] layer is placed at the correct position at the
- * end of the transition
- */
-class StatusBarLayerPositionAtEnd : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val subject = layerSubject.last()
-        subject
-            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversExactly(getExpectedStatusbarPosition(scenarioInstance))
-    }
-
-    // TODO: Maybe find another way to get the expected position that doesn't rely on use the data
-    // from the WM trace
-    // can we maybe dump another trace that just has system info for this purpose?
-    private fun getExpectedStatusbarPosition(scenarioInstance: IScenarioInstance): Region {
-        val wmState =
-            scenarioInstance.reader.readWmTrace()?.entries?.last()
-                ?: error("Missing wm trace entries")
-        val display =
-            wmState.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
-        TODO("return WindowUtils.getExpectedStatusBarPosition(display)")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtStart.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtStart.kt
deleted file mode 100644
index b73db2e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/StatusBarLayerPositionAtStart.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks if the [ComponentNameMatcher.STATUS_BAR] layer is placed at the correct position at the
- * start of the transition
- */
-class StatusBarLayerPositionAtStart : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        val subject = layerSubject.first()
-        subject
-            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversExactly(getExpectedStatusbarPosition(scenarioInstance))
-    }
-
-    // TODO: Maybe find another way to get the expected position that doesn't rely on use the data
-    // from the WM trace
-    // can we maybe dump another trace that just has system info for this purpose?
-    // TODO: Also this is duplicated code we can probably extract this out
-    private fun getExpectedStatusbarPosition(scenarioInstance: IScenarioInstance): Region {
-        val wmState =
-            scenarioInstance.reader.readWmTrace()?.entries?.last()
-                ?: error("Missing wm trace entries")
-        val display =
-            wmState.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
-        TODO("return WindowUtils.getExpectedStatusBarPosition(display)")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
deleted file mode 100644
index d552166..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleLayersShownMoreThanOneConsecutiveEntry.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-
-/**
- * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
- * entries.
- */
-class VisibleLayersShownMoreThanOneConsecutiveEntry : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(scenarioInstance: IScenarioInstance, layerSubject: LayersTraceSubject) {
-        layerSubject.visibleLayersShownMoreThanOneConsecutiveEntry().forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
deleted file mode 100644
index 30185c9..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/VisibleWindowsShownMoreThanOneConsecutiveEntry.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.AssertionTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/**
- * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
- * entries.
- */
-class VisibleWindowsShownMoreThanOneConsecutiveEntry : AssertionTemplate() {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject.visibleWindowsShownMoreThanOneConsecutiveEntry().forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesOutOfTop.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesOutOfTop.kt
deleted file mode 100644
index 9249209..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesOutOfTop.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks that [component] starts on top and moves out of top during the transition */
-open class WindowMovesOutOfTop(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowOnTop(component.build(scenarioInstance))
-            .then()
-            .isAppWindowNotOnTop(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesToTop.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesToTop.kt
deleted file mode 100644
index 1e6eb5e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/assertions/WindowMovesToTop.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.service.assertors.assertions
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.ComponentTemplate
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Checks that [component] starts not on top and moves to top during the transition */
-open class WindowMovesToTop(component: ComponentTemplate) :
-    AssertionTemplateWithComponent(component) {
-    /** {@inheritDoc} */
-    override fun doEvaluate(
-        scenarioInstance: IScenarioInstance,
-        wmSubject: WindowManagerTraceSubject
-    ) {
-        wmSubject
-            .isAppWindowNotOnTop(component.build(scenarioInstance))
-            .then()
-            .isAppWindowOnTop(component.build(scenarioInstance))
-            .forAllEntries()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/AssertionFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/AssertionFactory.kt
deleted file mode 100644
index 8ab4e10..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/AssertionFactory.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.factories
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-import com.android.server.wm.traces.common.service.config.FlickerServiceConfig
-
-open class AssertionFactory : IAssertionFactory {
-    override fun generateAssertionsFor(
-        scenarioInstance: IScenarioInstance
-    ): Collection<IFaasAssertion> {
-        val assertionTemplates =
-            FlickerServiceConfig.getScenarioConfigFor(scenarioInstance.type).assertionTemplates
-        return assertionTemplates.map { it.createAssertion(scenarioInstance) }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/CombinedAssertionFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/CombinedAssertionFactory.kt
deleted file mode 100644
index 5be584d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/CombinedAssertionFactory.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.factories
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-
-class CombinedAssertionFactory(private val factories: List<IAssertionFactory>) : IAssertionFactory {
-    override fun generateAssertionsFor(
-        scenarioInstance: IScenarioInstance
-    ): Collection<IFaasAssertion> {
-        return CrossPlatform.log.withTracing("CombinedAssertionFactory#generateAssertionsFor") {
-            factories.flatMap { it.generateAssertionsFor(scenarioInstance) }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/GeneratedAssertionsFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/GeneratedAssertionsFactory.kt
deleted file mode 100644
index 3b73c00..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/GeneratedAssertionsFactory.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.factories
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-
-class GeneratedAssertionsFactory : IAssertionFactory {
-    override fun generateAssertionsFor(
-        scenarioInstance: IScenarioInstance
-    ): Collection<IFaasAssertion> {
-        // TODO: Implement
-        return emptyList()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/IAssertionFactory.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/IAssertionFactory.kt
deleted file mode 100644
index 5c845fc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/factories/IAssertionFactory.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.factories
-
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-
-interface IAssertionFactory {
-    // What format should the returned assertion be? Probably want to have data about the stability
-    // of the assertion here for the AssertionRunner to then decide how to run them based on the
-    // config? Or do we want it to be prefiltered by the AssertionFactories?
-    fun generateAssertionsFor(scenarioInstance: IScenarioInstance): Collection<IFaasAssertion>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/AssertionRunner.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/AssertionRunner.kt
deleted file mode 100644
index ebc8e72..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/AssertionRunner.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.runners
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-
-class AssertionRunner : IAssertionRunner {
-    override fun execute(assertions: List<IFaasAssertion>): List<IAssertionResult> {
-        return CrossPlatform.log.withTracing("AssertionRunner#execute") {
-            assertions.map { it.evaluate() }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/IAssertionRunner.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/IAssertionRunner.kt
deleted file mode 100644
index 8560c98..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/assertors/runners/IAssertionRunner.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.assertors.runners
-
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-
-interface IAssertionRunner {
-    fun execute(assertions: List<IFaasAssertion>): List<IAssertionResult>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/AssertionTemplates.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/config/AssertionTemplates.kt
deleted file mode 100644
index 0c91ed2..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/AssertionTemplates.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.config
-
-import com.android.server.wm.traces.common.service.assertors.Components
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerBecomesInvisible
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerBecomesVisible
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerCoversFullScreenAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerCoversFullScreenAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerIsInvisibleAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerIsInvisibleAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerIsVisibleAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppLayerIsVisibleAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowBecomesInvisible
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowBecomesTopWindow
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowBecomesVisible
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowCoversFullScreenAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowCoversFullScreenAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowIsInvisibleAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowIsInvisibleAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowIsTopWindowAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowIsVisibleAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowIsVisibleAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowOnTopAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.AppWindowOnTopAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.EntireScreenCoveredAlways
-import com.android.server.wm.traces.common.service.assertors.assertions.EntireScreenCoveredAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.EntireScreenCoveredAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.LayerIsVisibleAlways
-import com.android.server.wm.traces.common.service.assertors.assertions.LayerIsVisibleAtEnd
-import com.android.server.wm.traces.common.service.assertors.assertions.LayerIsVisibleAtStart
-import com.android.server.wm.traces.common.service.assertors.assertions.NonAppWindowIsVisibleAlways
-import com.android.server.wm.traces.common.service.assertors.assertions.VisibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.traces.common.service.assertors.assertions.VisibleWindowsShownMoreThanOneConsecutiveEntry
-
-object AssertionTemplates {
-    val COMMON_ASSERTIONS =
-        listOf(
-            EntireScreenCoveredAtStart(),
-            EntireScreenCoveredAtEnd(),
-            EntireScreenCoveredAlways(),
-            VisibleWindowsShownMoreThanOneConsecutiveEntry(),
-            VisibleLayersShownMoreThanOneConsecutiveEntry(),
-        )
-
-    val NAV_BAR_ASSERTIONS =
-        listOf(
-            LayerIsVisibleAtStart(Components.NAV_BAR),
-            LayerIsVisibleAtEnd(Components.NAV_BAR),
-            NonAppWindowIsVisibleAlways(Components.NAV_BAR),
-        )
-
-    val STATUS_BAR_ASSERTIONS =
-        listOf(
-            NonAppWindowIsVisibleAlways(Components.STATUS_BAR),
-            LayerIsVisibleAlways(Components.STATUS_BAR),
-        )
-
-    val APP_LAUNCH_ASSERTIONS =
-        COMMON_ASSERTIONS +
-            listOf(
-                AppLayerIsInvisibleAtStart(Components.OPENING_APP),
-                AppLayerIsVisibleAtEnd(Components.OPENING_APP),
-                AppLayerBecomesVisible(Components.OPENING_APP),
-                AppWindowBecomesVisible(Components.OPENING_APP),
-                AppWindowBecomesTopWindow(Components.OPENING_APP),
-            )
-
-    val APP_CLOSE_ASSERTIONS =
-        COMMON_ASSERTIONS +
-            listOf(
-                AppLayerIsVisibleAtStart(Components.CLOSING_APP),
-                AppLayerIsInvisibleAtEnd(Components.CLOSING_APP),
-                AppWindowIsVisibleAtStart(Components.CLOSING_APP),
-                AppWindowIsInvisibleAtEnd(Components.CLOSING_APP),
-                AppLayerBecomesInvisible(Components.CLOSING_APP),
-                AppWindowBecomesInvisible(Components.CLOSING_APP),
-                AppWindowIsTopWindowAtStart(Components.CLOSING_APP),
-            )
-
-    val APP_LAUNCH_FROM_HOME_ASSERTIONS =
-        APP_LAUNCH_ASSERTIONS +
-            listOf(
-                AppLayerIsVisibleAtStart(Components.LAUNCHER),
-                AppLayerIsInvisibleAtEnd(Components.LAUNCHER),
-            )
-
-    val APP_CLOSE_TO_HOME_ASSERTIONS =
-        APP_CLOSE_ASSERTIONS +
-            listOf(
-                AppLayerIsInvisibleAtStart(Components.LAUNCHER),
-                AppLayerIsVisibleAtEnd(Components.LAUNCHER),
-                AppWindowIsInvisibleAtStart(Components.LAUNCHER),
-                AppWindowIsVisibleAtEnd(Components.LAUNCHER),
-                AppWindowBecomesTopWindow(Components.LAUNCHER),
-            )
-
-    val APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS =
-        COMMON_ASSERTIONS +
-            APP_LAUNCH_ASSERTIONS +
-            listOf(
-                // None specific to opening from notification yet
-                )
-
-    val LAUNCHER_QUICK_SWITCH_ASSERTIONS =
-        COMMON_ASSERTIONS +
-            APP_LAUNCH_ASSERTIONS +
-            APP_CLOSE_ASSERTIONS +
-            listOf(
-                AppWindowCoversFullScreenAtStart(Components.CLOSING_APP),
-                AppLayerCoversFullScreenAtStart(Components.CLOSING_APP),
-                AppWindowCoversFullScreenAtEnd(Components.OPENING_APP),
-                AppLayerCoversFullScreenAtEnd(Components.OPENING_APP),
-                AppWindowOnTopAtStart(Components.CLOSING_APP),
-                AppWindowOnTopAtEnd(Components.OPENING_APP),
-                AppWindowBecomesInvisible(Components.CLOSING_APP),
-                AppLayerBecomesInvisible(Components.CLOSING_APP),
-                AppWindowBecomesVisible(Components.OPENING_APP),
-                AppLayerBecomesVisible(Components.OPENING_APP),
-            )
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FaasScenarioType.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FaasScenarioType.kt
deleted file mode 100644
index ff1339b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FaasScenarioType.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * 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 com.android.server.wm.traces.common.service.config
-
-enum class FaasScenarioType {
-    COMMON,
-    LAUNCHER_APP_LAUNCH_FROM_ICON,
-    APP_CLOSE_TO_HOME,
-    LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-    NOTIFICATION_APP_START,
-    LAUNCHER_QUICK_SWITCH
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FlickerServiceConfig.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FlickerServiceConfig.kt
deleted file mode 100644
index 0de4f8f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/FlickerServiceConfig.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.config
-
-import com.android.server.wm.traces.common.events.CujType
-import com.android.server.wm.traces.common.service.assertors.IAssertionTemplate
-import com.android.server.wm.traces.common.service.config.AssertionTemplates.APP_CLOSE_TO_HOME_ASSERTIONS
-import com.android.server.wm.traces.common.service.config.AssertionTemplates.APP_LAUNCH_FROM_HOME_ASSERTIONS
-import com.android.server.wm.traces.common.service.config.AssertionTemplates.APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS
-import com.android.server.wm.traces.common.service.config.AssertionTemplates.COMMON_ASSERTIONS
-import com.android.server.wm.traces.common.service.config.AssertionTemplates.LAUNCHER_QUICK_SWITCH_ASSERTIONS
-import com.android.server.wm.traces.common.service.config.TransitionFilters.CLOSE_APP_TO_LAUNCHER_FILTER
-import com.android.server.wm.traces.common.service.config.TransitionFilters.OPEN_APP_TRANSITION_FILTER
-import com.android.server.wm.traces.common.service.config.TransitionFilters.QUICK_SWITCH_TRANSITION_FILTER
-import com.android.server.wm.traces.common.service.config.TransitionFilters.QUICK_SWITCH_TRANSITION_MERGE
-import com.android.server.wm.traces.common.service.extractors.EntireTraceExtractor
-import com.android.server.wm.traces.common.service.extractors.IScenarioExtractor
-import com.android.server.wm.traces.common.service.extractors.TaggedScenarioExtractor
-import com.android.server.wm.traces.common.service.extractors.TransitionMatcher
-
-object FlickerServiceConfig {
-    /** EDIT THIS CONFIG TO ADD SCENARIOS TO FAAS */
-    fun getScenarioConfigFor(type: FaasScenarioType): ScenarioConfig =
-        when (type) {
-            FaasScenarioType.COMMON ->
-                ScenarioConfig(
-                    extractor = EntireTraceExtractor(FaasScenarioType.COMMON),
-                    assertionTemplates = COMMON_ASSERTIONS
-                )
-            FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_ICON ->
-                ScenarioConfig(
-                    extractor =
-                        TaggedScenarioExtractor(
-                            targetTag = CujType.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
-                            type,
-                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
-                        ),
-                    assertionTemplates = APP_LAUNCH_FROM_HOME_ASSERTIONS
-                )
-            FaasScenarioType.APP_CLOSE_TO_HOME ->
-                ScenarioConfig(
-                    extractor =
-                        TaggedScenarioExtractor(
-                            targetTag = CujType.CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
-                            type,
-                            transitionMatcher = TransitionMatcher(CLOSE_APP_TO_LAUNCHER_FILTER)
-                        ),
-                    assertionTemplates = APP_CLOSE_TO_HOME_ASSERTIONS
-                )
-            FaasScenarioType.NOTIFICATION_APP_START ->
-                ScenarioConfig(
-                    extractor =
-                        TaggedScenarioExtractor(
-                            targetTag = CujType.CUJ_NOTIFICATION_APP_START,
-                            type,
-                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
-                        ),
-                    assertionTemplates = APP_LAUNCH_FROM_NOTIFICATION_ASSERTIONS
-                )
-            FaasScenarioType.LAUNCHER_QUICK_SWITCH ->
-                ScenarioConfig(
-                    extractor =
-                        TaggedScenarioExtractor(
-                            targetTag = CujType.CUJ_LAUNCHER_QUICK_SWITCH,
-                            type,
-                            transitionMatcher =
-                                TransitionMatcher(
-                                    QUICK_SWITCH_TRANSITION_FILTER,
-                                    finalTransform = QUICK_SWITCH_TRANSITION_MERGE
-                                )
-                        ),
-                    assertionTemplates = LAUNCHER_QUICK_SWITCH_ASSERTIONS
-                )
-            FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_RECENTS ->
-                ScenarioConfig(
-                    extractor =
-                        TaggedScenarioExtractor(
-                            targetTag = CujType.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-                            type,
-                            transitionMatcher = TransitionMatcher(OPEN_APP_TRANSITION_FILTER)
-                        ),
-                    assertionTemplates = APP_LAUNCH_FROM_HOME_ASSERTIONS
-                )
-        }
-
-    fun getExtractors(): List<IScenarioExtractor> {
-        return FaasScenarioType.values().map { getScenarioConfigFor(it).extractor }
-    }
-}
-
-data class ScenarioConfig(
-    val extractor: IScenarioExtractor,
-    val assertionTemplates: Collection<IAssertionTemplate>
-)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/TransitionFilters.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/config/TransitionFilters.kt
deleted file mode 100644
index da2979a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/config/TransitionFilters.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.config
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.service.extractors.TransitionsTransform
-import com.android.server.wm.traces.common.transition.Transition
-
-object TransitionFilters {
-    val OPEN_APP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
-        ts.filter { t ->
-            t.changes.any {
-                it.transitMode == Transition.Companion.Type.OPEN || // cold launch
-                it.transitMode == Transition.Companion.Type.TO_FRONT // warm launch
-            }
-        }
-    }
-
-    val CLOSE_APP_TO_LAUNCHER_FILTER: TransitionsTransform = { ts, _, reader ->
-        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
-        val layers =
-            layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
-        val launcherLayers = layers.filter { ComponentNameMatcher.LAUNCHER.layerMatchesAnyOf(it) }
-
-        ts.filter { t ->
-            t.changes.any {
-                it.transitMode == Transition.Companion.Type.CLOSE ||
-                    it.transitMode == Transition.Companion.Type.TO_BACK
-            } &&
-                t.changes.any { change ->
-                    launcherLayers.any { it.id == change.layerId }
-                    change.transitMode == Transition.Companion.Type.TO_FRONT
-                }
-        }
-    }
-
-    val QUICK_SWITCH_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
-        ts.filter { t ->
-            t.changes.size == 2 &&
-                t.changes.any { it.transitMode == Transition.Companion.Type.TO_BACK } &&
-                t.changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT }
-        }
-    }
-
-    val QUICK_SWITCH_TRANSITION_MERGE: TransitionsTransform = { transitions, _, _ ->
-        require(transitions.size == 2) { "Expected 2 transitions but got ${transitions.size}" }
-
-        require(transitions[0].changes.size == 2)
-        require(transitions[0].changes.any { it.transitMode == Transition.Companion.Type.TO_BACK })
-        require(transitions[0].changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT })
-
-        require(transitions[1].changes.size == 2)
-        require(transitions[1].changes.any { it.transitMode == Transition.Companion.Type.TO_BACK })
-        require(transitions[1].changes.any { it.transitMode == Transition.Companion.Type.TO_FRONT })
-
-        val candidateWallpaper1 =
-            transitions[0].changes.first { it.transitMode == Transition.Companion.Type.TO_FRONT }
-        val candidateWallpaper2 =
-            transitions[1].changes.first { it.transitMode == Transition.Companion.Type.TO_BACK }
-
-        require(candidateWallpaper1.layerId == candidateWallpaper2.layerId)
-
-        val closingAppChange =
-            transitions[0].changes.first { it.transitMode == Transition.Companion.Type.TO_BACK }
-        val openingAppChange =
-            transitions[1].changes.first { it.transitMode == Transition.Companion.Type.TO_FRONT }
-
-        listOf(
-            Transition(
-                start = transitions[0].start,
-                sendTime = transitions[0].sendTime,
-                startTransactionId = transitions[0].startTransactionId,
-                // NOTE: Relies on the implementation detail that the second
-                // finishTransaction is merged into the first and applied.
-                finishTransactionId = transitions[0].finishTransactionId,
-                changes = listOf(closingAppChange, openingAppChange),
-                played = true,
-                aborted = false
-            )
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/CombinedScenarioExtractor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/CombinedScenarioExtractor.kt
deleted file mode 100644
index 61548d6..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/CombinedScenarioExtractor.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.IScenarioInstance
-
-class CombinedScenarioExtractor(private val extractors: List<IScenarioExtractor>) :
-    IScenarioExtractor {
-    override fun extract(reader: IReader): List<IScenarioInstance> {
-        return CrossPlatform.log.withTracing("CombinedScenarioExtractor#extract") {
-            extractors.flatMap { it.extract(reader) }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/EntireTraceExtractor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/EntireTraceExtractor.kt
deleted file mode 100644
index c24fe2f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/EntireTraceExtractor.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.ScenarioInstance
-import com.android.server.wm.traces.common.service.config.FaasScenarioType
-
-class EntireTraceExtractor(val type: FaasScenarioType) : IScenarioExtractor {
-    override fun extract(reader: IReader): List<ScenarioInstance> {
-        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
-
-        return listOf(
-            ScenarioInstance(
-                type,
-                startRotation = layersTrace.first().physicalDisplay?.transform?.getRotation()
-                        ?: error("Missing display"),
-                endRotation = layersTrace.last().physicalDisplay?.transform?.getRotation()
-                        ?: error("Missing display"),
-                startTimestamp = layersTrace.entries.first().timestamp,
-                endTimestamp = layersTrace.entries.last().timestamp,
-                associatedCuj = null,
-                associatedTransition = null,
-                reader = reader
-            )
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/IScenarioExtractor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/IScenarioExtractor.kt
deleted file mode 100644
index c314d05..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/IScenarioExtractor.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.IScenarioInstance
-
-interface IScenarioExtractor {
-    fun extract(reader: IReader): List<IScenarioInstance>
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/ITransitionMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/ITransitionMatcher.kt
deleted file mode 100644
index d8791aa..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/ITransitionMatcher.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.events.Cuj
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.transition.Transition
-
-interface ITransitionMatcher {
-    fun getTransition(cujEntry: Cuj, reader: IReader): Transition?
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TaggedScenarioExtractor.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TaggedScenarioExtractor.kt
deleted file mode 100644
index df3e080..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TaggedScenarioExtractor.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.events.CujType
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.ScenarioInstance
-import com.android.server.wm.traces.common.service.config.FaasScenarioType
-import kotlin.math.max
-
-class TaggedScenarioExtractor(
-    val targetTag: CujType,
-    val type: FaasScenarioType,
-    val transitionMatcher: ITransitionMatcher,
-    val associatedTransitionRequired: Boolean = true
-) : IScenarioExtractor {
-    override fun extract(reader: IReader): List<ScenarioInstance> {
-
-        val wmTrace = reader.readWmTrace()
-        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
-        val cujTrace = reader.readCujTrace() ?: error("Missing CUJ trace")
-
-        val targetCujEntries =
-            cujTrace.entries.filter { it.cuj === targetTag }.filter { !it.canceled }
-
-        if (targetCujEntries.isEmpty()) {
-            // No scenarios to extract here
-            return emptyList()
-        }
-
-        return targetCujEntries.map { cujEntry ->
-            val associatedTransition = transitionMatcher.getTransition(cujEntry, reader)
-
-            require(
-                cujEntry.startTimestamp.hasAllTimestamps && cujEntry.endTimestamp.hasAllTimestamps
-            )
-
-            // There is a delay between when we flag that transition as finished with the CUJ tags
-            // and when it is actually finished on the SF side. We try and account for that by
-            // checking when the finish transaction is actually applied.
-            // TODO: Figure out how to get the vSyncId that the Jank tracker actually gets to avoid
-            //       relying on the transition and have a common end point.
-            val finishTransactionAppliedTimestamp =
-                if (associatedTransition != null) {
-                    val transactionsTrace =
-                        reader.readTransactionsTrace() ?: error("Missing transactions trace")
-                    val finishTransaction =
-                        transactionsTrace.allTransactions.firstOrNull {
-                            it.id == associatedTransition.finishTransactionId
-                        }
-                            ?: error("Finish transaction not found")
-                    require(
-                        layersTrace.entries.first().vSyncId <= finishTransaction.appliedVSyncId &&
-                            finishTransaction.appliedVSyncId <= layersTrace.entries.last().vSyncId
-                    ) { "Finish transaction not in layer trace" }
-                    val appliedInLayerEntry =
-                        layersTrace.entries.first { it.vSyncId >= finishTransaction.appliedVSyncId }
-                    appliedInLayerEntry.timestamp
-                } else {
-                    CrossPlatform.timestamp.min()
-                }
-
-            val wmEntryAtTransitionFinished =
-                wmTrace?.first { it.timestamp >= finishTransactionAppliedTimestamp }
-
-            val startTimestamp = cujEntry.startTimestamp
-            val endTimestamp =
-                CrossPlatform.timestamp.from(
-                    elapsedNanos =
-                        max(
-                            cujEntry.endTimestamp.elapsedNanos,
-                            wmEntryAtTransitionFinished?.timestamp?.elapsedNanos ?: -1L
-                        ),
-                    systemUptimeNanos =
-                        max(
-                            cujEntry.endTimestamp.systemUptimeNanos,
-                            finishTransactionAppliedTimestamp.systemUptimeNanos
-                        ),
-                    unixNanos =
-                        max(
-                            cujEntry.endTimestamp.unixNanos,
-                            finishTransactionAppliedTimestamp.unixNanos
-                        )
-                )
-
-            ScenarioInstance(
-                type,
-                startRotation =
-                    layersTrace
-                        .getEntryAt(startTimestamp)
-                        .displays
-                        .first { !it.isVirtual && it.layerStackSpace.isNotEmpty }
-                        .transform
-                        .getRotation(),
-                endRotation =
-                    layersTrace
-                        .getEntryAt(endTimestamp)
-                        .displays
-                        .first { !it.isVirtual && it.layerStackSpace.isNotEmpty }
-                        .transform
-                        .getRotation(),
-                startTimestamp = startTimestamp,
-                endTimestamp = endTimestamp,
-                associatedCuj = cujEntry.cuj,
-                associatedTransition = associatedTransition,
-                reader = reader.slice(startTimestamp, endTimestamp)
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TransitionMatcher.kt b/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TransitionMatcher.kt
deleted file mode 100644
index a880254..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/service/extractors/TransitionMatcher.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.service.extractors
-
-import com.android.server.wm.traces.common.MILLISECOND_AS_NANOSECONDS
-import com.android.server.wm.traces.common.events.Cuj
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.extractors.TransitionTransforms.inCujRangeFilter
-import com.android.server.wm.traces.common.service.extractors.TransitionTransforms.mergeTrampolineTransitions
-import com.android.server.wm.traces.common.service.extractors.TransitionTransforms.noOpTransitionsTransform
-import com.android.server.wm.traces.common.transition.Transition
-
-typealias TransitionsTransform =
-    (transitions: List<Transition>, cujEntry: Cuj, reader: IReader) -> List<Transition>
-
-class TransitionMatcher(
-    private val mainTransform: TransitionsTransform,
-    private val finalTransform: TransitionsTransform = noOpTransitionsTransform,
-    private val associatedTransitionRequired: Boolean = true,
-    // Transformations applied, in order, to all transitions the reader returns to end up with the
-    // targeted transition.
-    private val transforms: List<TransitionsTransform> =
-        listOf(mainTransform, inCujRangeFilter, mergeTrampolineTransitions, finalTransform)
-) : ITransitionMatcher {
-    override fun getTransition(cujEntry: Cuj, reader: IReader): Transition? {
-        val transitionsTrace = reader.readTransitionsTrace() ?: error("Missing transitions trace")
-
-        val completeTransitions = transitionsTrace.entries.filter { !it.isIncomplete }
-
-        val matchedTransitions =
-            transforms.fold(completeTransitions) { transitions, transform ->
-                transform(transitions, cujEntry, reader)
-            }
-
-        require(!associatedTransitionRequired || matchedTransitions.isNotEmpty()) {
-            "Required an associated transition for " +
-                "${cujEntry.cuj.name}(${cujEntry.startTimestamp},${cujEntry.endTimestamp}) " +
-                "but no transition left after all filters from: " +
-                "[\n${transitionsTrace.entries.joinToString(",\n").prependIndent()}\n]!"
-        }
-
-        require(!associatedTransitionRequired || matchedTransitions.size == 1) {
-            "Got too many associated transitions expected only 1."
-        }
-
-        return if (matchedTransitions.isNotEmpty()) matchedTransitions[0] else null
-    }
-}
-
-object TransitionTransforms {
-    val inCujRangeFilter: TransitionsTransform = { transitions, cujEntry, _ ->
-        transitions.filter { transition ->
-            val transitionSentWithinCujTags =
-                cujEntry.startTimestamp <= transition.sendTime &&
-                    transition.sendTime <= cujEntry.endTimestamp
-
-            // TODO: This threshold should be made more robust. Can fail to match on slower devices.
-            val toleranceNanos = 50 * MILLISECOND_AS_NANOSECONDS
-            val transitionSentJustBeforeCujStart =
-                cujEntry.startTimestamp - toleranceNanos <= transition.sendTime &&
-                    transition.sendTime <= cujEntry.startTimestamp
-
-            return@filter transitionSentWithinCujTags || transitionSentJustBeforeCujStart
-        }
-    }
-
-    val mergeTrampolineTransitions: TransitionsTransform = { transitions, _, reader ->
-        require(transitions.size <= 2)
-        if (
-            transitions.size == 2 &&
-                isTrampolinedOpenTransition(transitions[0], transitions[1], reader)
-        ) {
-            // Remove the trampoline transition
-            listOf(transitions[0])
-        } else {
-            transitions
-        }
-    }
-
-    val noOpTransitionsTransform: TransitionsTransform = { transitions, _, _ -> transitions }
-
-    private fun isTrampolinedOpenTransition(
-        firstTransition: Transition,
-        secondTransition: Transition,
-        reader: IReader
-    ): Boolean {
-        val candidateTaskLayers =
-            firstTransition.changes
-                .filter {
-                    it.transitMode == Transition.Companion.Type.OPEN ||
-                        it.transitMode == Transition.Companion.Type.TO_FRONT
-                }
-                .map { it.layerId }
-        if (candidateTaskLayers.isEmpty()) {
-            return false
-        }
-
-        require(candidateTaskLayers.size == 1) {
-            "Unhandled case (more than 1 task candidate) in isTrampolinedOpenTransition()"
-        }
-
-        val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
-        val layers =
-            layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
-
-        val candidateTaskLayerId = candidateTaskLayers[0]
-        val candidateTaskLayer = layers.first { it.id == candidateTaskLayerId }
-        if (!candidateTaskLayer.name.contains("Task")) {
-            return false
-        }
-
-        val candidateTrampolinedActivities =
-            secondTransition.changes
-                .filter { it.transitMode == Transition.Companion.Type.CLOSE }
-                .map { it.layerId }
-        val candidateTargetActivities =
-            secondTransition.changes
-                .filter {
-                    it.transitMode == Transition.Companion.Type.OPEN ||
-                        it.transitMode == Transition.Companion.Type.TO_FRONT
-                }
-                .map { it.layerId }
-        if (candidateTrampolinedActivities.isEmpty() || candidateTargetActivities.isEmpty()) {
-            return false
-        }
-
-        require(candidateTargetActivities.size == 1) {
-            "Unhandled case (more than 1 trampolined candidate) in " +
-                "isTrampolinedOpenTransition()"
-        }
-        require(candidateTargetActivities.size == 1) {
-            "Unhandled case (more than 1 target candidate) in isTrampolinedOpenTransition()"
-        }
-
-        val candidateTrampolinedActivityId = candidateTargetActivities[0]
-        val candidateTrampolinedActivity = layers.first { it.id == candidateTrampolinedActivityId }
-        if (candidateTrampolinedActivity.parent?.id != candidateTaskLayerId) {
-            return false
-        }
-
-        val candidateTargetActivityId = candidateTargetActivities[0]
-        val candidateTargetActivity = layers.first { it.id == candidateTargetActivityId }
-        if (candidateTargetActivity.parent?.id != candidateTaskLayerId) {
-            return false
-        }
-
-        return true
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubject.kt
deleted file mode 100644
index 56abd2d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubject.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects
-
-import com.android.server.wm.traces.common.assertions.Fact
-
-/** Subject for flicker checks */
-data class CheckSubject<T>(
-    private val actualValue: T?,
-    private val subject: FlickerSubject,
-    private val lazyMessage: () -> String,
-) {
-    fun isEqual(expectedValue: T?) {
-        if (actualValue != expectedValue) {
-            failWithFactForExpectedValue(Fact("expected to be equal to", expectedValue))
-        }
-    }
-
-    fun isNotEqual(expectedValue: T?) {
-        if (actualValue == expectedValue) {
-            failWithFactForExpectedValue(Fact("expected to be different from", expectedValue))
-        }
-    }
-
-    fun isNull() {
-        if (actualValue != null) {
-            failWithFactForExpectedValue(Fact("expected to be", null))
-        }
-    }
-
-    fun isNotNull() {
-        if (actualValue == null) {
-            failWithFactForExpectedValue(Fact("expected not to be", null))
-        }
-    }
-
-    fun isLower(expectedValue: T?) {
-        if (
-            actualValue == null ||
-                expectedValue == null ||
-                (actualValue as Comparable<T>) >= expectedValue
-        ) {
-            failWithFactForExpectedValue(Fact("expected to be lower than", expectedValue))
-        }
-    }
-
-    fun isLowerOrEqual(expectedValue: T?) {
-        if (
-            actualValue == null ||
-                expectedValue == null ||
-                (actualValue as Comparable<T>) > expectedValue
-        ) {
-            failWithFactForExpectedValue(Fact("expected to be lower or equal to", expectedValue))
-        }
-    }
-
-    fun isGreater(expectedValue: T) {
-        if (
-            actualValue == null ||
-                expectedValue == null ||
-                (actualValue as Comparable<T>) <= expectedValue
-        ) {
-            failWithFactForExpectedValue(Fact("expected to be greater than", expectedValue))
-        }
-    }
-
-    fun isGreaterOrEqual(expectedValue: T) {
-        if (
-            actualValue == null ||
-                expectedValue == null ||
-                (actualValue as Comparable<T>) < expectedValue
-        ) {
-            failWithFactForExpectedValue(Fact("expected to be greater or equal to", expectedValue))
-        }
-    }
-
-    fun <U> contains(expectedValue: U) {
-        if (actualValue !is List<*> || !(actualValue as List<U>).contains(expectedValue)) {
-            failWithFactForExpectedValue(Fact("expected to contain", expectedValue))
-        }
-    }
-
-    private fun failWithFactForExpectedValue(factForExpectedValue: Fact) {
-        val facts =
-            listOf(
-                Fact("Assertion failed", lazyMessage()),
-                Fact("Actual value", actualValue),
-                factForExpectedValue,
-            )
-        subject.fail(facts)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubjectBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubjectBuilder.kt
deleted file mode 100644
index 9c6e29c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/CheckSubjectBuilder.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects
-
-/** Subject builder for flicker checks */
-data class CheckSubjectBuilder(
-    private val subject: FlickerSubject,
-    private val lazyMessage: () -> String
-) {
-    fun <T> that(actual: T?): CheckSubject<T> {
-        return CheckSubject(actual, subject, lazyMessage)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubject.kt
deleted file mode 100644
index 8e29dfb..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubject.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.Fact
-
-/** Base subject for flicker assertions */
-abstract class FlickerSubject {
-    abstract val timestamp: Timestamp
-    protected abstract val parent: FlickerSubject?
-
-    protected abstract val selfFacts: List<Fact>
-    val completeFacts: List<Fact>
-        get() {
-            val facts = selfFacts.toMutableList()
-            parent?.run {
-                val ancestorFacts = this.completeFacts
-                facts.addAll(ancestorFacts)
-            }
-            return facts
-        }
-
-    /**
-     * Fails an assertion on a subject
-     *
-     * @param reason for the failure
-     */
-    open fun fail(reason: List<Fact>): FlickerSubject = apply {
-        require(reason.isNotEmpty()) { "Failure should contain at least 1 fact" }
-        throw FlickerSubjectException(timestamp, reason + completeFacts)
-    }
-
-    fun fail(reason: Fact, vararg rest: Fact): FlickerSubject = apply {
-        val what = mutableListOf(reason).also { it.addAll(rest) }
-        fail(what)
-    }
-
-    /**
-     * Fails an assertion on a subject
-     *
-     * @param reason for the failure
-     */
-    fun fail(reason: Fact): FlickerSubject = apply { fail(listOf(reason)) }
-
-    /**
-     * Fails an assertion on a subject
-     *
-     * @param reason for the failure
-     */
-    fun fail(reason: String): FlickerSubject = apply { fail(Fact("Reason", reason)) }
-
-    /**
-     * Fails an assertion on a subject
-     *
-     * @param reason for the failure
-     * @param value for the failure
-     */
-    fun fail(reason: String, value: Any): FlickerSubject = apply { fail(Fact(reason, value)) }
-
-    /**
-     * Fails an assertion on a subject
-     *
-     * @param reason for the failure
-     */
-    fun fail(reason: Throwable) {
-        if (reason is FlickerSubjectException) {
-            throw reason
-        } else {
-            throw FlickerSubjectException(timestamp, completeFacts, reason)
-        }
-    }
-
-    fun check(lazyMessage: () -> String): CheckSubjectBuilder {
-        return CheckSubjectBuilder(this, lazyMessage)
-    }
-
-    companion object {
-        const val ASSERTION_TAG = "Assertion"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubjectException.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubjectException.kt
deleted file mode 100644
index df7ac26..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerSubjectException.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.Fact
-
-/** Exception thrown by flicker subjects */
-class FlickerSubjectException(
-    timestamp: Timestamp,
-    val facts: List<Fact>,
-    override val cause: Throwable? = null
-) : AssertionError() {
-    override val message = buildString {
-        appendLine(errorType)
-
-        appendLine()
-        appendLine("What? ")
-        cause?.message?.split("\n")?.forEach {
-            appendLine()
-            appendLine(it.prependIndent("\t"))
-        }
-
-        appendLine()
-        appendLine("Where?")
-        appendLine(timestamp.toString().prependIndent("\t"))
-
-        appendLine()
-        appendLine("Facts")
-        facts.forEach { appendLine(it.toString().prependIndent("\t")) }
-    }
-
-    private val errorType: String =
-        if (cause == null) "Flicker assertion error" else "Unknown error"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerTraceSubject.kt
deleted file mode 100644
index 08f3577..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/FlickerTraceSubject.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.assertions.AssertionsChecker
-import com.android.server.wm.traces.common.assertions.Fact
-
-/** Base subject for flicker trace assertions */
-abstract class FlickerTraceSubject<EntrySubject : FlickerSubject> : FlickerSubject() {
-    override val timestamp
-        get() = subjects.firstOrNull()?.timestamp ?: CrossPlatform.timestamp.empty()
-    override val selfFacts by lazy {
-        listOf(
-            Fact("Trace start", subjects.firstOrNull()?.timestamp),
-            Fact("Trace end", subjects.lastOrNull()?.timestamp)
-        )
-    }
-
-    protected val assertionsChecker = AssertionsChecker<EntrySubject>()
-    private var newAssertionBlock = true
-
-    abstract val subjects: List<EntrySubject>
-
-    fun hasAssertions() = !assertionsChecker.isEmpty()
-
-    /**
-     * Adds a new assertion block (if preceded by [then]) or appends an assertion to the latest
-     * existing assertion block
-     *
-     * @param name Assertion name
-     * @param isOptional If this assertion is optional or must pass
-     */
-    protected fun addAssertion(
-        name: String,
-        isOptional: Boolean = false,
-        assertion: (EntrySubject) -> Unit
-    ) {
-        if (newAssertionBlock) {
-            assertionsChecker.add(name, isOptional, assertion)
-        } else {
-            assertionsChecker.append(name, isOptional, assertion)
-        }
-        newAssertionBlock = false
-    }
-
-    /** Run the assertions for all trace entries */
-    fun forAllEntries() {
-        require(subjects.isNotEmpty()) { "Trace is empty" }
-        assertionsChecker.test(subjects)
-    }
-
-    /** User-defined entry point for the first trace entry */
-    fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty")
-
-    /** User-defined entry point for the last trace entry */
-    fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty")
-
-    /**
-     * Signal that the last assertion set is complete. The next assertion added will start a new set
-     * of assertions.
-     *
-     * E.g.: checkA().then().checkB()
-     *
-     * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
-     * after checkA passes.
-     */
-    open fun then(): FlickerTraceSubject<EntrySubject> = apply { startAssertionBlock() }
-
-    /**
-     * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
-     * end of the trace without passing any assertion, return a failure with the name/reason from
-     * the first assertion
-     *
-     * @return
-     */
-    open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> = apply {
-        assertionsChecker.skipUntilFirstAssertion()
-    }
-
-    /**
-     * Signal that the last assertion set is complete. The next assertion added will start a new set
-     * of assertions.
-     *
-     * E.g.: checkA().then().checkB()
-     *
-     * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
-     * after checkA passes.
-     */
-    private fun startAssertionBlock() {
-        newAssertionBlock = true
-    }
-
-    /**
-     * Checks whether all the trace entries on the list are visible for more than one consecutive
-     * entry
-     *
-     * Ignore the first and last trace subjects. This is necessary because WM and SF traces log
-     * entries only when a change occurs.
-     *
-     * If the trace starts immediately before an animation or if it stops immediately after one, the
-     * first and last entry may contain elements that are visible only for that entry. Those
-     * elements, however, are not flickers, since they existed on the screen before or after the
-     * test.
-     *
-     * @param [visibleEntriesProvider] a list of all the entries with their name and index
-     */
-    protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
-        visibleEntriesProvider: (EntrySubject) -> Set<String>
-    ) {
-        if (subjects.isEmpty()) {
-            return
-        }
-        // Duplicate the first and last trace subjects to prevent them from triggering failures
-        // since WM and SF traces log entries only when a change occurs
-        val firstState = subjects.first()
-        val lastState = subjects.last()
-        val subjects =
-            subjects.toMutableList().also {
-                it.add(lastState)
-                it.add(0, firstState)
-            }
-        var lastVisible = visibleEntriesProvider(subjects.first())
-        val lastNew = lastVisible.toMutableSet()
-
-        // first subject was already taken
-        subjects.drop(1).forEachIndexed { index, entrySubject ->
-            val currentVisible = visibleEntriesProvider(entrySubject)
-            val newVisible = currentVisible.filter { it !in lastVisible }
-            lastNew.removeAll(currentVisible)
-
-            if (lastNew.isNotEmpty()) {
-                val prevEntry = subjects[index]
-                prevEntry.fail("$lastNew is not visible for 2 entries")
-            }
-            lastNew.addAll(newVisible)
-            lastVisible = currentVisible
-        }
-
-        if (lastNew.isNotEmpty()) {
-            val lastEntry = subjects.last()
-            lastEntry.fail("$lastNew is not visible for 2 entries")
-        }
-    }
-
-    override fun toString(): String =
-        "${this::class.simpleName}" +
-            "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/EventLogSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/EventLogSubject.kt
deleted file mode 100644
index 869c4ee..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/EventLogSubject.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.eventlog
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-
-/** Truth subject for [FocusEvent] objects. */
-class EventLogSubject(val eventLog: EventLog) : FlickerSubject() {
-    override val timestamp = CrossPlatform.timestamp.empty()
-    override val parent = null
-    override val selfFacts by lazy {
-        listOf(
-            Fact("Trace start", "${eventLog.entries.firstOrNull()?.timestamp}"),
-            Fact("Trace end", "${eventLog.entries.lastOrNull()?.timestamp}")
-        )
-    }
-
-    private val subjects by lazy { eventLog.focusEvents.map { FocusEventSubject(it, this) } }
-
-    private val _focusChanges by lazy {
-        val focusList = mutableListOf<String>()
-        eventLog.focusEvents.firstOrNull { !it.hasFocus() }?.let { focusList.add(it.window) }
-        focusList + eventLog.focusEvents.filter { it.hasFocus() }.map { it.window }
-    }
-
-    fun focusChanges(vararg windows: String) = apply {
-        if (windows.isNotEmpty()) {
-            val focusChanges =
-                _focusChanges.dropWhile { !it.contains(windows.first()) }.take(windows.size)
-            val success =
-                windows.size <= focusChanges.size &&
-                    focusChanges.zip(windows).all { (focus, search) -> focus.contains(search) }
-
-            if (!success) {
-                fail(
-                    Fact("Expected", windows.joinToString(",")),
-                    Fact("Found", focusChanges.joinToString(","))
-                )
-            }
-        }
-    }
-
-    fun focusDoesNotChange() = apply { check(_focusChanges.isEmpty()) { "Focus does not change" } }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/FocusEventSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/FocusEventSubject.kt
deleted file mode 100644
index a207c4c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/eventlog/FocusEventSubject.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.eventlog
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-
-class FocusEventSubject(val event: FocusEvent, override val parent: EventLogSubject?) :
-    FlickerSubject() {
-    override val timestamp = event.timestamp
-    override val selfFacts by lazy { listOf(Fact(event.toString())) }
-
-    fun hasFocus() {
-        check { "Has focus" }.that(event.hasFocus()).isEqual(true)
-    }
-
-    fun hasNotFocus() {
-        check { "Has not focus" }.that(event.hasFocus()).isEqual(false)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/ILayerSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/ILayerSubject.kt
deleted file mode 100644
index 517e779..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/ILayerSubject.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.layers
-
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.layers.Layer
-
-/** Base interface for Layer trace and state assertions */
-interface ILayerSubject<LayerSubjectType, RegionSubjectType> {
-    /** Asserts that the current SurfaceFlinger state doesn't contain layers */
-    fun isEmpty(): LayerSubjectType
-
-    /** Asserts that the current SurfaceFlinger state contains layers */
-    fun isNotEmpty(): LayerSubjectType
-
-    /**
-     * Obtains the region occupied by all layers matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
-     * Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise, calculates the
-     * visible region when the information is not available from the CE
-     */
-    fun visibleRegion(
-        componentMatcher: IComponentMatcher? = null,
-        useCompositionEngineRegionOnly: Boolean = true
-    ): RegionSubjectType
-
-    /**
-     * Asserts the state contains a [Layer] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun contains(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts the state doesn't contain a [Layer] matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    fun notContains(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts that a [Layer] matching [componentMatcher] is visible.
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isVisible(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts that a [Layer] matching [componentMatcher] doesn't exist or is invisible.
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isInvisible(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts that the entry contains a visible splash screen [Layer] for a [layer] matching
-     * [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isSplashScreenVisibleFor(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts that a [Layer] matching [componentMatcher] has a color set on it.
-     *
-     * @param componentMatcher Components to search
-     */
-    fun hasColor(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Asserts that all [Layer]s matching [componentMatcher] have a no color set on them.
-     *
-     * @param componentMatcher Components to search
-     */
-    fun hasNoColor(componentMatcher: IComponentMatcher): LayerSubjectType
-
-    /**
-     * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name] containing
-     * [name] in [frameNumber].
-     *
-     * Always returns a subject, event when the layer doesn't exist. To verify if layer actually
-     * exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
-     *
-     * @return LayerSubject that can be used to make assertions on a single layer matching [name]
-     * and [frameNumber].
-     */
-    fun layer(name: String, frameNumber: Long): LayerSubject?
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerSubject.kt
deleted file mode 100644
index e9c73f7..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerSubject.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.layers
-
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-
-/**
- * Subject for [Layer] objects, used to make assertions over behaviors that occur on a single layer
- * of a SurfaceFlinger state.
- *
- * To make assertions over a layer from a state it is recommended to create a subject using
- * [LayerTraceEntrySubject.layer](layerName)
- *
- * Alternatively, it is also possible to use [LayerSubject](myLayer).
- *
- * Example:
- * ```
- *    val trace = LayersTraceParser().parse(myTraceFile)
- *    val subject = LayersTraceSubject(trace).first()
- *        .layer("ValidLayer")
- *        .exists()
- *        .hasBufferSize(BUFFER_SIZE)
- *        .invoke { myCustomAssertion(this) }
- * ```
- */
-class LayerSubject(
-    public override val parent: FlickerSubject,
-    override val timestamp: Timestamp,
-    val layer: Layer
-) : FlickerSubject() {
-    val isVisible: Boolean
-        get() = layer.isVisible
-    val isInvisible: Boolean
-        get() = !layer.isVisible
-    val name: String
-        get() = layer.name
-
-    /** Visible region calculated by the Composition Engine */
-    val visibleRegion: RegionSubject
-        get() = RegionSubject(layer.visibleRegion, this, timestamp)
-
-    val visibilityReason: Array<String>
-        get() = layer.visibilityReason
-
-    /**
-     * Visible region calculated by the Composition Engine (when available) or calculated based on
-     * the layer bounds and transform
-     */
-    val screenBounds: RegionSubject
-        get() = RegionSubject(layer.screenBounds, this, timestamp)
-
-    override val selfFacts = listOf(Fact("Frame", layer.currFrame), Fact("Layer", layer.name))
-
-    /** If the [layer] exists, executes a custom [assertion] on the current subject */
-    operator fun invoke(assertion: (Layer) -> Unit): LayerSubject = apply { assertion(this.layer) }
-
-    /**
-     * Asserts that current subject has an [Layer.activeBuffer]
-     *
-     * @param expected expected buffer size
-     */
-    fun hasBufferSize(expected: Size): LayerSubject = apply {
-        val bufferSize = Size.from(layer.activeBuffer.width, layer.activeBuffer.height)
-        check { "Buffer size" }.that(bufferSize).isEqual(expected)
-    }
-
-    /**
-     * Asserts that current subject has an [Layer.screenBounds]
-     *
-     * @param size expected layer bounds size
-     */
-    fun hasLayerSize(size: Size): LayerSubject = apply {
-        val layerSize =
-            Size.from(layer.screenBounds.width.toInt(), layer.screenBounds.height.toInt())
-        check { "Number of layers" }.that(layerSize).isEqual(size)
-    }
-
-    /**
-     * Asserts that current subject has an [Layer.effectiveScalingMode] equals to
-     * [expectedScalingMode]
-     */
-    fun hasScalingMode(expectedScalingMode: Int): LayerSubject = apply {
-        val actualScalingMode = layer.effectiveScalingMode
-        check(actualScalingMode == expectedScalingMode) {
-            "Scaling mode. Actual: $actualScalingMode, expected: $expectedScalingMode"
-        }
-    }
-
-    /**
-     * Asserts that current subject has an [Layer.bufferTransform] orientation equals to
-     * [expectedOrientation]
-     */
-    fun hasBufferOrientation(expectedOrientation: Int): LayerSubject = apply {
-        // see Transform::getOrientation
-        val bufferTransformType = layer.bufferTransform.type ?: 0
-        val actualOrientation = (bufferTransformType shr 8) and 0xFF
-        check(actualOrientation == expectedOrientation) {
-            "Buffer orientation. Actual: $actualOrientation, expected: $expectedOrientation"
-        }
-    }
-
-    override fun toString(): String {
-        return "Layer:${layer.name} frame#${layer.currFrame}"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerTraceEntrySubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerTraceEntrySubject.kt
deleted file mode 100644
index f592518..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayerTraceEntrySubject.kt
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.layers
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-
-/**
- * Subject for [BaseLayerTraceEntry] objects, used to make assertions over behaviors that occur on a
- * single SurfaceFlinger state.
- *
- * To make assertions over a specific state from a trace it is recommended to create a subject using
- * [LayersTraceSubject](myTrace) and select the specific state using:
- * ```
- *     [LayersTraceSubject.first]
- *     [LayersTraceSubject.last]
- *     [LayersTraceSubject.entry]
- * ```
- * Alternatively, it is also possible to use [LayerTraceEntrySubject](myState).
- *
- * Example:
- * ```
- *    val trace = LayersTraceParser().parse(myTraceFile)
- *    val subject = LayersTraceSubject(trace).first()
- *        .contains("ValidLayer")
- *        .notContains("ImaginaryLayer")
- *        .coversExactly(DISPLAY_AREA)
- *        .invoke { myCustomAssertion(this) }
- * ```
- */
-class LayerTraceEntrySubject(
-    val entry: BaseLayerTraceEntry,
-    val trace: LayersTrace? = null,
-    override val parent: FlickerSubject? = null
-) : FlickerSubject(), ILayerSubject<LayerTraceEntrySubject, RegionSubject> {
-    override val timestamp = entry.timestamp
-    override val selfFacts = listOf(Fact("SF State", entry))
-
-    val subjects by lazy { entry.flattenedLayers.map { LayerSubject(this, timestamp, it) } }
-
-    /** Executes a custom [assertion] on the current subject */
-    operator fun invoke(assertion: (BaseLayerTraceEntry) -> Unit): LayerTraceEntrySubject = apply {
-        assertion(this.entry)
-    }
-
-    /** {@inheritDoc} */
-    override fun isEmpty(): LayerTraceEntrySubject = apply {
-        check(entry.flattenedLayers.isEmpty()) { "SF state is empty" }
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotEmpty(): LayerTraceEntrySubject = apply {
-        check(entry.flattenedLayers.isNotEmpty()) { "SF state is not empty" }
-    }
-
-    /** See [visibleRegion] */
-    fun visibleRegion(): RegionSubject =
-        visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = true)
-
-    /** See [visibleRegion] */
-    fun visibleRegion(componentMatcher: IComponentMatcher): RegionSubject =
-        visibleRegion(componentMatcher, useCompositionEngineRegionOnly = true)
-
-    /** {@inheritDoc} */
-    override fun visibleRegion(
-        componentMatcher: IComponentMatcher?,
-        useCompositionEngineRegionOnly: Boolean
-    ): RegionSubject {
-        val selectedLayers =
-            if (componentMatcher == null) {
-                // No filters so use all subjects
-                subjects
-            } else {
-                subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
-            }
-
-        if (selectedLayers.isEmpty()) {
-            val str = componentMatcher?.toLayerIdentifier() ?: "<any>"
-            fail(
-                listOf(
-                    Fact(ASSERTION_TAG, "visibleRegion($str)"),
-                    Fact("Use composition engine region", useCompositionEngineRegionOnly),
-                    Fact("Could not find layers", str)
-                )
-            )
-        }
-
-        val visibleLayers = selectedLayers.filter { it.isVisible }
-        return if (useCompositionEngineRegionOnly) {
-            val visibleAreas = visibleLayers.mapNotNull { it.layer.visibleRegion }.toTypedArray()
-            RegionSubject(visibleAreas, this, timestamp)
-        } else {
-            val visibleAreas = visibleLayers.map { it.layer.screenBounds }.toTypedArray()
-            RegionSubject(visibleAreas, this, timestamp)
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun contains(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        val found = componentMatcher.layerMatchesAnyOf(entry.flattenedLayers)
-        if (!found) {
-            fail(
-                Fact(ASSERTION_TAG, "contains(${componentMatcher.toLayerIdentifier()})"),
-                Fact("Could not find", componentMatcher.toLayerIdentifier())
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun notContains(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        val layers = subjects.map { it.layer }
-        val notContainsComponent =
-            componentMatcher.check(layers) { matchedLayers -> matchedLayers.isEmpty() }
-
-        if (notContainsComponent) {
-            return@apply
-        }
-
-        val failedEntries = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
-        val failureFacts =
-            mutableListOf(
-                Fact(ASSERTION_TAG, "notContains(${componentMatcher.toLayerIdentifier()})")
-            )
-        failedEntries.forEach { entry -> failureFacts.add(Fact("Found", entry)) }
-        failedEntries.firstOrNull()?.fail(failureFacts)
-    }
-
-    /** {@inheritDoc} */
-    override fun isVisible(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        contains(componentMatcher)
-        val layers = subjects.map { it.layer }
-        val hasVisibleSubject =
-            componentMatcher.check(layers) { matchedLayers ->
-                matchedLayers.any { layer -> layer.isVisible }
-            }
-
-        if (hasVisibleSubject) {
-            return@apply
-        }
-
-        val matchingSubjects = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
-        val failedEntries = matchingSubjects.filter { it.isInvisible }
-        val failureFacts =
-            mutableListOf(Fact(ASSERTION_TAG, "isVisible(${componentMatcher.toLayerIdentifier()})"))
-
-        failedEntries.forEach { entry ->
-            failureFacts.add(Fact("Is Invisible", entry))
-            failureFacts.addAll(entry.visibilityReason.map { Fact("Invisibility reason", it) })
-        }
-        failedEntries.firstOrNull()?.fail(failureFacts)
-    }
-
-    /** {@inheritDoc} */
-    override fun isInvisible(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        val layers = subjects.map { it.layer }
-        val hasInvisibleComponent =
-            componentMatcher.check(layers) { componentLayers ->
-                componentLayers.all { layer ->
-                    subjects.first { subject -> subject.layer == layer }.isInvisible
-                }
-            }
-
-        if (hasInvisibleComponent) {
-            return@apply
-        }
-
-        val matchingSubjects = subjects.filter { componentMatcher.layerMatchesAnyOf(it.layer) }
-        val failedEntries = matchingSubjects.filter { it.isVisible }
-        val failureFacts =
-            mutableListOf(
-                Fact(ASSERTION_TAG, "isInvisible(${componentMatcher.toLayerIdentifier()})")
-            )
-        failureFacts.addAll(failedEntries.map { Fact("Is Visible", it) })
-        failedEntries.firstOrNull()?.fail(failureFacts)
-    }
-
-    /** {@inheritDoc} */
-    override fun isSplashScreenVisibleFor(
-        componentMatcher: IComponentMatcher
-    ): LayerTraceEntrySubject = apply {
-        var target: FlickerSubject? = null
-        var reason: Fact? = null
-
-        val matchingLayer =
-            subjects.map { it.layer }.filter { componentMatcher.layerMatchesAnyOf(it) }
-        val matchingActivityRecords = matchingLayer.mapNotNull { getActivityRecordFor(it) }
-
-        if (matchingActivityRecords.isEmpty()) {
-            fail(
-                Fact(
-                    ASSERTION_TAG,
-                    "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})"
-                ),
-                Fact("Could not find Activity Record layer", componentMatcher.toLayerIdentifier())
-            )
-            return this
-        }
-
-        // Check the matched activity record layers for containing splash screens
-        for (layer in matchingActivityRecords) {
-            val splashScreenContainers = layer.children.filter { it.name.contains("Splash Screen") }
-            val splashScreenLayers =
-                splashScreenContainers.flatMap {
-                    it.children.filter { childLayer -> childLayer.name.contains("Splash Screen") }
-                }
-
-            if (splashScreenLayers.all { it.isHiddenByParent || !it.isVisible }) {
-                reason = Fact("No splash screen visible for", layer.name)
-                target = subjects.first { it.layer == layer }
-                continue
-            }
-            reason = null
-            target = null
-            break
-        }
-
-        reason?.run {
-            target?.fail(
-                Fact(
-                    ASSERTION_TAG,
-                    "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})"
-                ),
-                reason
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun hasColor(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        contains(componentMatcher)
-
-        val hasColorLayer =
-            componentMatcher.check(subjects.map { it.layer }) {
-                it.any { layer -> layer.color.isNotEmpty }
-            }
-
-        if (!hasColorLayer) {
-            fail(Fact(ASSERTION_TAG, "hasColor(${componentMatcher.toLayerIdentifier()})"))
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun hasNoColor(componentMatcher: IComponentMatcher): LayerTraceEntrySubject = apply {
-        val hasNoColorLayer =
-            componentMatcher.check(subjects.map { it.layer }) {
-                it.all { layer -> layer.color.isEmpty }
-            }
-
-        if (!hasNoColorLayer) {
-            fail(Fact(ASSERTION_TAG, "hasNoColor(${componentMatcher.toLayerIdentifier()})"))
-        }
-    }
-
-    /** See [layer] */
-    fun layer(componentMatcher: IComponentMatcher): LayerSubject? {
-        return layer { componentMatcher.layerMatchesAnyOf(it) }
-    }
-
-    /** {@inheritDoc} */
-    override fun layer(name: String, frameNumber: Long): LayerSubject? {
-        return layer { it.name.contains(name) && it.currFrame == frameNumber }
-    }
-
-    /**
-     * Obtains a [LayerSubject] for the first occurrence of a [Layer] matching [predicate] or throws
-     * and error if the layer doesn't exist
-     *
-     * @param predicate to search for a layer
-     *
-     * @return [LayerSubject] that can be used to make assertions
-     */
-    fun layer(predicate: (Layer) -> Boolean): LayerSubject? =
-        subjects.firstOrNull { predicate(it.layer) }
-
-    private fun getActivityRecordFor(layer: Layer): Layer? {
-        if (layer.name.startsWith("ActivityRecord{")) {
-            return layer
-        }
-        val parent = layer.parent ?: return null
-        return getActivityRecordFor(parent)
-    }
-
-    override fun toString(): String {
-        return "LayerTraceEntrySubject($entry)"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayersTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayersTraceSubject.kt
deleted file mode 100644
index b3d4261..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/layers/LayersTraceSubject.kt
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.layers
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.EdgeExtensionComponentMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.region.RegionTrace
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-import com.android.server.wm.traces.common.subjects.region.RegionTraceSubject
-
-/**
- * Subject for [LayersTrace] 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
- * [LayersTraceSubject](myTrace).
- *
- * Example:
- * ```
- *    val trace = LayersTraceParser().parse(myTraceFile)
- *    val subject = LayersTraceSubject(trace)
- *        .contains("ValidLayer")
- *        .notContains("ImaginaryLayer")
- *        .coversExactly(DISPLAY_AREA)
- *        .forAllEntries()
- * ```
- * Example2:
- * ```
- *    val trace = LayersTraceParser().parse(myTraceFile)
- *    val subject = LayersTraceSubject(trace) {
- *        check("Custom check") { myCustomAssertion(this) }
- *    }
- * ```
- */
-class LayersTraceSubject(
-    val trace: LayersTrace,
-    override val parent: LayersTraceSubject? = null,
-    val facts: Collection<Fact> = emptyList()
-) :
-    FlickerTraceSubject<LayerTraceEntrySubject>(),
-    ILayerSubject<LayersTraceSubject, RegionTraceSubject> {
-
-    override val selfFacts by lazy {
-        val allFacts = super.selfFacts.toMutableList()
-        allFacts.addAll(facts)
-        allFacts
-    }
-
-    override val subjects by lazy { trace.entries.map { LayerTraceEntrySubject(it, trace, this) } }
-
-    /** {@inheritDoc} */
-    override fun then(): LayersTraceSubject = apply { super.then() }
-
-    /** {@inheritDoc} */
-    override fun isEmpty(): LayersTraceSubject = apply {
-        check { "Trace is empty" }.that(trace.isEmpty()).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotEmpty(): LayersTraceSubject = apply {
-        check { "Trace is not empty" }.that(trace.isNotEmpty()).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun layer(name: String, frameNumber: Long): LayerSubject {
-        val value = subjects.firstNotNullOfOrNull { it.layer(name, frameNumber) }
-        if (value == null) {
-            fail("Layer does not exist $name")
-        }
-        requireNotNull(value)
-        return value
-    }
-
-    /** @return List of [LayerSubject]s matching [name] in the order they appear on the trace */
-    fun layers(name: String): List<LayerSubject> =
-        subjects.mapNotNull { it.layer { layer -> layer.name.contains(name) } }
-
-    /**
-     * @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
-     */
-    fun layers(predicate: (Layer) -> Boolean): List<LayerSubject> =
-        subjects.mapNotNull { it.layer { layer -> predicate(layer) } }
-
-    /** Checks that all visible layers are shown for more than one consecutive entry */
-    fun visibleLayersShownMoreThanOneConsecutiveEntry(
-        ignoreLayers: List<IComponentMatcher> =
-            listOf(
-                ComponentNameMatcher.SPLASH_SCREEN,
-                ComponentNameMatcher.SNAPSHOT,
-                ComponentNameMatcher.IME_SNAPSHOT,
-                EdgeExtensionComponentMatcher()
-            )
-    ): LayersTraceSubject = apply {
-        visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
-            subject.entry.visibleLayers
-                .filter {
-                    ignoreLayers.none { componentMatcher -> componentMatcher.layerMatchesAnyOf(it) }
-                }
-                .map { it.name }
-                .toSet()
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun notContains(componentMatcher: IComponentMatcher): LayersTraceSubject =
-        notContains(componentMatcher, isOptional = false)
-
-    /** See [notContains] */
-    fun notContains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
-        apply {
-            addAssertion("notContains(${componentMatcher.toLayerIdentifier()})", isOptional) {
-                it.notContains(componentMatcher)
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun contains(componentMatcher: IComponentMatcher): LayersTraceSubject =
-        contains(componentMatcher, isOptional = false)
-
-    /** See [contains] */
-    fun contains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
-        apply {
-            addAssertion("contains(${componentMatcher.toLayerIdentifier()})", isOptional) {
-                it.contains(componentMatcher)
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun isVisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
-        isVisible(componentMatcher, isOptional = false)
-
-    /** See [isVisible] */
-    fun isVisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
-        apply {
-            addAssertion("isVisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
-                it.isVisible(componentMatcher)
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun isInvisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
-        isInvisible(componentMatcher, isOptional = false)
-
-    /** See [isInvisible] */
-    fun isInvisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
-        apply {
-            addAssertion("isInvisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
-                it.isInvisible(componentMatcher)
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun isSplashScreenVisibleFor(componentMatcher: IComponentMatcher): LayersTraceSubject =
-        isSplashScreenVisibleFor(componentMatcher, isOptional = false)
-
-    /** {@inheritDoc} */
-    override fun hasColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
-        addAssertion("hasColor(${componentMatcher.toLayerIdentifier()})") {
-            it.hasColor(componentMatcher)
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun hasNoColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
-        addAssertion("hasNoColor(${componentMatcher.toLayerIdentifier()})") {
-            it.hasNoColor(componentMatcher)
-        }
-    }
-
-    /** See [isSplashScreenVisibleFor] */
-    fun isSplashScreenVisibleFor(
-        componentMatcher: IComponentMatcher,
-        isOptional: Boolean
-    ): LayersTraceSubject = apply {
-        addAssertion(
-            "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})",
-            isOptional
-        ) { it.isSplashScreenVisibleFor(componentMatcher) }
-    }
-
-    /** See [visibleRegion] */
-    fun visibleRegion(): RegionTraceSubject =
-        visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = false)
-
-    /** See [visibleRegion] */
-    fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject =
-        visibleRegion(componentMatcher, useCompositionEngineRegionOnly = false)
-
-    /** {@inheritDoc} */
-    override fun visibleRegion(
-        componentMatcher: IComponentMatcher?,
-        useCompositionEngineRegionOnly: Boolean
-    ): RegionTraceSubject {
-        val regionTrace =
-            RegionTrace(
-                componentMatcher,
-                subjects
-                    .map {
-                        it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly)
-                            .regionEntry
-                    }
-                    .toTypedArray()
-            )
-        return RegionTraceSubject(regionTrace, this)
-    }
-
-    /** Executes a custom [assertion] on the current subject */
-    operator fun invoke(
-        name: String,
-        isOptional: Boolean = false,
-        assertion: (LayerTraceEntrySubject) -> Unit
-    ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
-
-    fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject =
-        hasFrameSequence(ComponentNameMatcher("", name), frameNumbers)
-
-    fun hasFrameSequence(
-        componentMatcher: IComponentMatcher,
-        frameNumbers: Iterable<Long>
-    ): LayersTraceSubject = apply {
-        val firstFrame = frameNumbers.first()
-        val entries =
-            trace.entries
-                .asSequence()
-                // map entry to buffer layers with name
-                .map { it.getLayerWithBuffer(componentMatcher) }
-                // removing all entries without the layer
-                .filterNotNull()
-                // removing all entries with the same frame number
-                .distinctBy { it.currFrame }
-                // drop until the first frame we are interested in
-                .dropWhile { layer -> layer.currFrame != firstFrame }
-
-        var numFound = 0
-        val frameNumbersMatch =
-            entries
-                .zip(frameNumbers.asSequence()) { layer, frameNumber ->
-                    numFound++
-                    layer.currFrame == frameNumber
-                }
-                .all { it }
-        val allFramesFound = frameNumbers.count() == numFound
-        if (!allFramesFound || !frameNumbersMatch) {
-            val message =
-                "Could not find Layer:" +
-                    componentMatcher.toLayerIdentifier() +
-                    " with frame sequence:" +
-                    frameNumbers.joinToString(",") +
-                    " Found:\n" +
-                    entries.joinToString("\n")
-            fail(message)
-        }
-    }
-
-    /** Run the assertions for all trace entries within the specified time range */
-    fun forSystemUpTimeRange(startTime: Long, endTime: Long) {
-        val subjectsInRange =
-            subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime }
-        assertionsChecker.test(subjectsInRange)
-    }
-
-    /**
-     * User-defined entry point for the trace entry with [timestamp]
-     *
-     * @param timestamp of the entry
-     */
-    fun getEntryBySystemUpTime(
-        timestamp: Long,
-        byElapsedTimestamp: Boolean = false
-    ): LayerTraceEntrySubject {
-        return if (byElapsedTimestamp) {
-            subjects.first { it.entry.elapsedTimestamp == timestamp }
-        } else {
-            subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionSubject.kt
deleted file mode 100644
index 3b198d7..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionSubject.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.region
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.region.RegionEntry
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import kotlin.math.abs
-
-/** Subject for [Rect] objects, used to make assertions over behaviors that occur on a rectangle. */
-class RegionSubject(
-    override val parent: FlickerSubject?,
-    val regionEntry: RegionEntry,
-    override val timestamp: Timestamp
-) : FlickerSubject() {
-
-    /** Custom constructor for existing android regions */
-    constructor(
-        region: Region?,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(parent, RegionEntry(region ?: Region.EMPTY, timestamp), timestamp)
-
-    /** Custom constructor for existing rects */
-    constructor(
-        rect: Array<Rect>,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(Region(rect), parent, timestamp)
-
-    /** Custom constructor for existing rects */
-    constructor(
-        rect: Rect?,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(Region.from(rect), parent, timestamp)
-
-    /** Custom constructor for existing rects */
-    constructor(
-        rect: RectF?,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(rect?.toRect(), parent, timestamp)
-
-    /** Custom constructor for existing rects */
-    constructor(
-        rect: Array<RectF>,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(mergeRegions(rect.map { Region.from(it.toRect()) }.toTypedArray()), parent, timestamp)
-
-    /** Custom constructor for existing regions */
-    constructor(
-        regions: Array<Region>,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(mergeRegions(regions), parent, timestamp)
-
-    /**
-     * Custom constructor
-     *
-     * @param regionEntry to assert
-     * @param parent containing the entry
-     */
-    constructor(
-        regionEntry: RegionEntry?,
-        parent: FlickerSubject? = null,
-        timestamp: Timestamp
-    ) : this(regionEntry?.region, parent, timestamp)
-
-    val region = regionEntry.region
-
-    private val Rect.area
-        get() = this.width * this.height
-
-    override val selfFacts = listOf(Fact("Region - Covered", region.toString()))
-
-    /** {@inheritDoc} */
-    override fun fail(reason: List<Fact>): FlickerSubject {
-        val newReason = reason.toMutableList()
-        return super.fail(newReason)
-    }
-
-    /** Asserts that the current [Region] doesn't contain layers */
-    fun isEmpty(): RegionSubject = apply { check(regionEntry.region.isEmpty) { "Region is empty" } }
-
-    /** Asserts that the current [Region] doesn't contain layers */
-    fun isNotEmpty(): RegionSubject = apply {
-        check(regionEntry.region.isNotEmpty) { "Region is not empty" }
-    }
-
-    private fun assertLeftRightAndAreaEquals(other: Region) {
-        check { MSG_ERROR_LEFT_POSITION }.that(region.bounds.left).isEqual(other.bounds.left)
-        check { MSG_ERROR_RIGHT_POSITION }.that(region.bounds.right).isEqual(other.bounds.right)
-        check { MSG_ERROR_AREA }.that(region.bounds.area).isEqual(other.bounds.area)
-    }
-
-    /** Subtracts [other] from this subject [region] */
-    fun minus(other: Region): RegionSubject {
-        val remainingRegion = Region.from(this.region)
-        remainingRegion.op(other, Region.Op.XOR)
-        return RegionSubject(remainingRegion, this, timestamp)
-    }
-
-    /** Adds [other] to this subject [region] */
-    fun plus(other: Region): RegionSubject {
-        val remainingRegion = Region.from(this.region)
-        remainingRegion.op(other, Region.Op.UNION)
-        return RegionSubject(remainingRegion, this, timestamp)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller or equal to
-     * those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigherOrEqual(subject: RegionSubject): RegionSubject = apply {
-        isHigherOrEqual(subject.region)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are smaller or equal to those of
-     * [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigherOrEqual(other: Rect): RegionSubject = apply { isHigherOrEqual(Region.from(other)) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are smaller or equal to those of
-     * [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigherOrEqual(other: Region): RegionSubject = apply {
-        assertLeftRightAndAreaEquals(other)
-        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isLowerOrEqual(other.bounds.top)
-        check { MSG_ERROR_BOTTOM_POSITION }
-            .that(region.bounds.bottom)
-            .isLowerOrEqual(other.bounds.bottom)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater or equal to
-     * those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLowerOrEqual(subject: RegionSubject): RegionSubject = apply {
-        isLowerOrEqual(subject.region)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are greater or equal to those of
-     * [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLowerOrEqual(other: Rect): RegionSubject = apply { isLowerOrEqual(Region.from(other)) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are greater or equal to those of
-     * [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLowerOrEqual(other: Region): RegionSubject = apply {
-        assertLeftRightAndAreaEquals(other)
-        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isGreaterOrEqual(other.bounds.top)
-        check { MSG_ERROR_BOTTOM_POSITION }
-            .that(region.bounds.bottom)
-            .isGreaterOrEqual(other.bounds.bottom)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller than those
-     * of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigher(subject: RegionSubject): RegionSubject = apply { isHigher(subject.region) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are smaller than those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigher(other: Rect): RegionSubject = apply { isHigher(Region.from(other)) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are smaller than those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isHigher(other: Region): RegionSubject = apply {
-        assertLeftRightAndAreaEquals(other)
-        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isLower(other.bounds.top)
-        check { MSG_ERROR_BOTTOM_POSITION }.that(region.bounds.bottom).isLower(other.bounds.bottom)
-    }
-
-    /**
-     * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater than those
-     * of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLower(subject: RegionSubject): RegionSubject = apply { isLower(subject.region) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are greater than those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLower(other: Rect): RegionSubject = apply { isLower(Region.from(other)) }
-
-    /**
-     * Asserts that the top and bottom coordinates of [other] are greater than those of [region].
-     *
-     * Also checks that the left and right positions, as well as area, don't change
-     */
-    fun isLower(other: Region): RegionSubject = apply {
-        assertLeftRightAndAreaEquals(other)
-        check { MSG_ERROR_TOP_POSITION }.that(region.bounds.top).isGreater(other.bounds.top)
-        check { MSG_ERROR_BOTTOM_POSITION }
-            .that(region.bounds.bottom)
-            .isGreater(other.bounds.bottom)
-    }
-
-    /**
-     * Asserts that [region] covers at most [testRegion], that is, its area doesn't cover any point
-     * outside of [testRegion].
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtMost(testRegion: Region): RegionSubject = apply {
-        if (!region.coversAtMost(testRegion)) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Covered region", region),
-                Fact("Out-of-bounds region", region.outOfBoundsRegion(testRegion))
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] covers at most [testRect], that is, its area doesn't cover any point
-     * outside of [testRect].
-     *
-     * @param testRect Expected covered area
-     */
-    fun coversAtMost(testRect: Rect): RegionSubject = apply { coversAtMost(Region.from(testRect)) }
-
-    /**
-     * Asserts that [region] is not bigger than [testRegion], even if the regions don't overlap.
-     *
-     * @param testRegion Area to compare to
-     */
-    fun notBiggerThan(testRegion: Region): RegionSubject = apply {
-        val testArea = testRegion.bounds.area
-        val area = region.bounds.area
-
-        if (area > testArea) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Area of test region", testArea),
-                Fact("Covered region", region),
-                Fact("Area of region", area)
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] is positioned to the right and bottom from [testRegion], but the
-     * regions can overlap and [region] can be smaller than [testRegion]
-     *
-     * @param testRegion Area to compare to
-     * @param threshold Offset threshold by which the position might be off
-     */
-    fun isToTheRightBottom(testRegion: Region, threshold: Int): RegionSubject = apply {
-        val horizontallyPositionedToTheRight =
-            testRegion.bounds.left - threshold <= region.bounds.left
-        val verticallyPositionedToTheBottom = testRegion.bounds.top - threshold <= region.bounds.top
-
-        if (!horizontallyPositionedToTheRight || !verticallyPositionedToTheBottom) {
-            fail(Fact("Region to test", testRegion), Fact("Actual region", region))
-        }
-    }
-
-    /**
-     * Asserts that [region] covers at least [testRegion], that is, its area covers each point in
-     * the region
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtLeast(testRegion: Region): RegionSubject = apply {
-        if (!region.coversAtLeast(testRegion)) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Covered region", region),
-                Fact("Uncovered region", region.uncoveredRegion(testRegion))
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] covers at least [testRect], that is, its area covers each point in the
-     * region
-     *
-     * @param testRect Expected covered area
-     */
-    fun coversAtLeast(testRect: Rect): RegionSubject = apply {
-        coversAtLeast(Region.from(testRect))
-    }
-
-    /**
-     * Asserts that [region] covers at exactly [testRegion]
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversExactly(testRegion: Region): RegionSubject = apply {
-        val intersection = Region.from(region)
-        val isNotEmpty = intersection.op(testRegion, Region.Op.XOR)
-
-        if (isNotEmpty) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Covered region", region),
-                Fact("Uncovered region", intersection)
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] covers at exactly [testRect]
-     *
-     * @param testRect Expected covered area
-     */
-    fun coversExactly(testRect: Rect): RegionSubject = apply {
-        coversExactly(Region.from(testRect))
-    }
-
-    /**
-     * Asserts that [region] and [testRegion] overlap
-     *
-     * @param testRegion Other area
-     */
-    fun overlaps(testRegion: Region): RegionSubject = apply {
-        val intersection = Region.from(region)
-        val isEmpty = !intersection.op(testRegion, Region.Op.INTERSECT)
-
-        if (isEmpty) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Covered region", region),
-                Fact("Overlap region", intersection)
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] and [testRect] overlap
-     *
-     * @param testRect Other area
-     */
-    fun overlaps(testRect: Rect): RegionSubject = apply { overlaps(Region.from(testRect)) }
-
-    /**
-     * Asserts that [region] and [testRegion] don't overlap
-     *
-     * @param testRegion Other area
-     */
-    fun notOverlaps(testRegion: Region): RegionSubject = apply {
-        val intersection = Region.from(region)
-        val isEmpty = !intersection.op(testRegion, Region.Op.INTERSECT)
-
-        if (!isEmpty) {
-            fail(
-                Fact("Region to test", testRegion),
-                Fact("Covered region", region),
-                Fact("Overlap region", intersection)
-            )
-        }
-    }
-
-    /**
-     * Asserts that [region] and [testRect] don't overlap
-     *
-     * @param testRect Other area
-     */
-    fun notOverlaps(testRect: Rect): RegionSubject = apply { notOverlaps(Region.from(testRect)) }
-
-    /**
-     * Asserts that [region] and [other] have same aspect ratio, margin of error up to 0.1.
-     *
-     * @param other Other region
-     */
-    fun isSameAspectRatio(other: RegionSubject): RegionSubject = apply {
-        val aspectRatio = this.region.width.toFloat() / this.region.height
-        val otherAspectRatio = other.region.width.toFloat() / other.region.height
-        check { "Aspect Ratio Difference" }
-            .that(abs(aspectRatio - otherAspectRatio))
-            .isLowerOrEqual(0.1f)
-    }
-
-    companion object {
-        const val MSG_ERROR_TOP_POSITION = "Top position"
-        const val MSG_ERROR_BOTTOM_POSITION = "Bottom position"
-        const val MSG_ERROR_LEFT_POSITION = "Left position"
-        const val MSG_ERROR_RIGHT_POSITION = "Right position"
-        const val MSG_ERROR_AREA = "Rect area"
-
-        private fun mergeRegions(regions: Array<Region>): Region {
-            val result = Region.EMPTY
-            regions.forEach { region ->
-                region.rects.forEach { rect -> result.op(rect, Region.Op.UNION) }
-            }
-            return result
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionTraceSubject.kt
deleted file mode 100644
index 44667ca..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/region/RegionTraceSubject.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.region
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.region.RegionTrace
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-
-class RegionTraceSubject(val trace: RegionTrace, override val parent: FlickerSubject?) :
-    FlickerTraceSubject<RegionSubject>() {
-
-    override val subjects by lazy { trace.entries.map { RegionSubject(it, this, it.timestamp) } }
-
-    private val componentsAsString =
-        if (trace.components == null) {
-            "<any>"
-        } else {
-            "[${trace.components}]"
-        }
-
-    /**
-     * Asserts that the visible area covered by any element in the state covers at most [testRegion]
-     * , that is, if the area of no elements cover any point outside of [testRegion].
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtMost(testRegion: Region): RegionTraceSubject = apply {
-        addAssertion("coversAtMost($testRegion, $componentsAsString") {
-            it.coversAtMost(testRegion)
-        }
-    }
-
-    /**
-     * Asserts that the visible area covered by any element in the state covers at most [testRegion]
-     * , that is, if the area of no elements cover any point outside of [testRegion].
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtMost(testRegion: Rect): RegionTraceSubject = this.coversAtMost(testRegion)
-
-    /**
-     * Asserts that the visible area covered by any element in the state covers at least
-     * [testRegion], that is, if the area of its elements visible region covers each point in the
-     * region.
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtLeast(testRegion: Region): RegionTraceSubject = apply {
-        addAssertion("coversAtLeast($testRegion, $componentsAsString)") {
-            it.coversAtLeast(testRegion)
-        }
-    }
-
-    /**
-     * Asserts that the visible area covered by any element in the state covers at least
-     * [testRegion], that is, if the area of its elements visible region covers each point in the
-     * region.
-     *
-     * @param testRegion Expected covered area
-     */
-    fun coversAtLeast(testRegion: Rect): RegionTraceSubject =
-        this.coversAtLeast(Region.from(testRegion))
-
-    /**
-     * Asserts that the visible region of the trace entries is exactly [expectedVisibleRegion].
-     *
-     * @param expectedVisibleRegion Expected visible region of the layer
-     */
-    fun coversExactly(expectedVisibleRegion: Region): RegionTraceSubject = apply {
-        addAssertion("coversExactly($expectedVisibleRegion, $componentsAsString)") {
-            it.coversExactly(expectedVisibleRegion)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/IWindowManagerSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/IWindowManagerSubject.kt
deleted file mode 100644
index db61d0d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/IWindowManagerSubject.kt
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.wm
-
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-
-/** Base interface for WM trace and state assertions */
-interface IWindowManagerSubject<WMSubjectType, RegionSubjectType> {
-    /** Asserts the current WindowManager state doesn't contain [WindowState]s */
-    fun isEmpty(): WMSubjectType
-
-    /** Asserts the current WindowManager state contains [WindowState]s */
-    fun isNotEmpty(): WMSubjectType
-
-    /**
-     * Obtains the region occupied by all windows matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    fun visibleRegion(componentMatcher: IComponentMatcher? = null): RegionSubjectType
-
-    /**
-     * Asserts the state contains a [WindowState] matching [componentMatcher] above the app windows
-     *
-     * @param componentMatcher Component to search
-     */
-    fun containsAboveAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state contains a [WindowState] matching [componentMatcher] below the app windows
-     *
-     * @param componentMatcher Component to search
-     */
-    fun containsBelowAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state contains [WindowState]s matching [aboveWindowComponentMatcher] and
-     * [belowWindowComponentMatcher], and that [aboveWindowComponentMatcher] is above
-     * [belowWindowComponentMatcher]
-     *
-     * This assertion can be used, for example, to assert that a PIP window is shown above other
-     * apps.
-     *
-     * @param aboveWindowComponentMatcher name of the window that should be above
-     * @param belowWindowComponentMatcher name of the window that should be below
-     */
-    fun isAboveWindow(
-        aboveWindowComponentMatcher: IComponentMatcher,
-        belowWindowComponentMatcher: IComponentMatcher
-    ): WMSubjectType
-
-    /**
-     * Asserts the state contains a non-app [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Component to search
-     */
-    fun containsNonAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the top visible app window in the state matches [componentMatcher]
-     *
-     * @param componentMatcher Component to search
-     */
-    fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the top visible app window in the state doesn't match [componentMatcher]
-     *
-     * @param componentMatcher Component to search
-     */
-    fun isAppWindowNotOnTop(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the bounds of the [WindowState] matching [componentMatcher] don't overlap.
-     *
-     * @param componentMatcher Component to search
-     */
-    fun doNotOverlap(vararg componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state contains an app [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Component to search
-     */
-    fun containsAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the display with id [displayId] has rotation [rotation]
-     *
-     * @param rotation to assert
-     * @param displayId of the target display
-     */
-    fun hasRotation(
-        rotation: PlatformConsts.Rotation,
-        displayId: Int = PlatformConsts.DEFAULT_DISPLAY
-    ): WMSubjectType
-
-    /**
-     * Asserts the state contains a [WindowState] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun contains(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state doesn't contain a [WindowState] nor an [Activity] matching
-     * [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun notContainsAppWindow(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state doesn't contain a [WindowState] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun notContains(componentMatcher: IComponentMatcher): WMSubjectType
-
-    fun isRecentsActivityVisible(): WMSubjectType
-
-    fun isRecentsActivityInvisible(): WMSubjectType
-
-    /**
-     * Asserts the state is valid, that is, if it has:
-     * - a resumed activity
-     * - a focused activity
-     * - a focused window
-     * - a front window
-     * - a focused app
-     */
-    fun isValid(): WMSubjectType
-
-    /**
-     * Asserts the state contains a visible [WindowState] matching [componentMatcher].
-     *
-     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
-     * that it contains a visible [Activity] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isNonAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state contains a visible [WindowState] matching [componentMatcher].
-     *
-     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
-     * that it contains a visible [Activity] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /** Asserts the state contains no visible app windows. */
-    fun hasNoVisibleAppWindow(): WMSubjectType
-
-    /** Asserts the state contains no visible app windows. */
-    fun isKeyguardShowing(): WMSubjectType
-
-    /**
-     * Asserts the state contains an invisible window [WindowState] matching [componentMatcher].
-     *
-     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
-     * that it contains an invisible [Activity] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts the state contains an invisible window matching [componentMatcher].
-     *
-     * Also, if [componentMatcher] has a package name (i.e., is not a system component), also checks
-     * that it contains an invisible [Activity] matching [componentMatcher].
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isNonAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /** Asserts the state home activity is visible */
-    fun isHomeActivityVisible(): WMSubjectType
-
-    /** Asserts the state home activity is invisible */
-    fun isHomeActivityInvisible(): WMSubjectType
-
-    /**
-     * Asserts that [app] is the focused app
-     *
-     * @param app App to check
-     */
-    fun isFocusedApp(app: String): WMSubjectType
-
-    /**
-     * Asserts that [app] is not the focused app
-     *
-     * @param app App to check
-     */
-    fun isNotFocusedApp(app: String): WMSubjectType
-
-    /**
-     * Asserts that [componentMatcher] exists and is pinned (in PIP mode)
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isPinned(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Asserts that [componentMatcher] exists and is not pinned (not in PIP mode)
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isNotPinned(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Checks if the activity with matching [componentMatcher] is visible
-     *
-     * In the case that an app is stopped in the background (e.g. OS stopped it to release memory)
-     * the app window will not be immediately visible when switching back to the app. Checking if a
-     * snapshotStartingWindow is present for that app instead can decrease flakiness levels of the
-     * assertion.
-     *
-     * @param componentMatcher Component to search
-     */
-    fun isAppSnapshotStartingWindowVisibleFor(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Checks if the non-app window matching [componentMatcher] exists above the app windows and is
-     * visible
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isAboveAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Checks if the non-app window matching [componentMatcher] exists above the app windows and is
-     * invisible
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isAboveAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Checks if the non-app window matching [componentMatcher] exists below the app windows and is
-     * visible
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isBelowAppWindowVisible(componentMatcher: IComponentMatcher): WMSubjectType
-
-    /**
-     * Checks if the non-app window matching [componentMatcher] exists below the app windows and is
-     * invisible
-     *
-     * @param componentMatcher Components to search
-     */
-    fun isBelowAppWindowInvisible(componentMatcher: IComponentMatcher): WMSubjectType
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerStateSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerStateSubject.kt
deleted file mode 100644
index 7f344f3..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerStateSubject.kt
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.wm
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-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.FlickerSubject
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-
-/**
- * Subject for [WindowManagerState] objects, used to make assertions over behaviors that occur on a
- * single WM state.
- *
- * To make assertions over a specific state from a trace it is recommended to create a subject using
- * [WindowManagerTraceSubject](myTrace) and select the specific state using:
- * ```
- *     [WindowManagerTraceSubject.first]
- *     [WindowManagerTraceSubject.last]
- *     [WindowManagerTraceSubject.entry]
- * ```
- * Alternatively, it is also possible to use [WindowManagerStateSubject](myState).
- *
- * Example:
- * ```
- *    val trace = WindowManagerTraceParser().parse(myTraceFile)
- *    val subject = WindowManagerTraceSubject(trace).first()
- *        .contains("ValidWindow")
- *        .notContains("ImaginaryWindow")
- *        .showsAboveAppWindow("NavigationBar")
- *        .invoke { myCustomAssertion(this) }
- * ```
- */
-class WindowManagerStateSubject(
-    val wmState: WindowManagerState,
-    val trace: WindowManagerTraceSubject? = null,
-    override val parent: FlickerSubject? = null
-) : FlickerSubject(), IWindowManagerSubject<WindowManagerStateSubject, RegionSubject> {
-    override val timestamp = wmState.timestamp
-    override val selfFacts = listOf(Fact("WM State", wmState))
-
-    val subjects by lazy { wmState.windowStates.map { WindowStateSubject(this, timestamp, it) } }
-
-    val appWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.appWindows.contains(it.windowState) }
-
-    val nonAppWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.nonAppWindows.contains(it.windowState) }
-
-    val aboveAppWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.aboveAppWindows.contains(it.windowState) }
-
-    val belowAppWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.belowAppWindows.contains(it.windowState) }
-
-    val visibleWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.visibleWindows.contains(it.windowState) }
-
-    val visibleAppWindows: List<WindowStateSubject>
-        get() = subjects.filter { wmState.visibleAppWindows.contains(it.windowState) }
-
-    /** Executes a custom [assertion] on the current subject */
-    operator fun invoke(assertion: (WindowManagerState) -> Unit): WindowManagerStateSubject =
-        apply {
-            assertion(this.wmState)
-        }
-
-    /** {@inheritDoc} */
-    override fun isEmpty(): WindowManagerStateSubject = apply {
-        check { "WM state is empty" }.that(subjects.isEmpty()).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotEmpty(): WindowManagerStateSubject = apply {
-        check { "WM state is not empty" }.that(subjects.isEmpty()).isEqual(false)
-    }
-
-    /** {@inheritDoc} */
-    override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionSubject {
-        val selectedWindows =
-            if (componentMatcher == null) {
-                // No filters so use all subjects
-                subjects
-            } else {
-                subjects.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
-            }
-
-        if (selectedWindows.isEmpty()) {
-            val str = componentMatcher?.toWindowIdentifier() ?: "<any>"
-            fail(Fact(ASSERTION_TAG, "visibleRegion($str)"), Fact("Could not find windows", str))
-        }
-
-        val visibleWindows = selectedWindows.filter { it.isVisible }
-        val visibleRegions = visibleWindows.map { it.windowState.frameRegion }.toTypedArray()
-        return RegionSubject(visibleRegions, this, timestamp)
-    }
-
-    /** {@inheritDoc} */
-    override fun containsAboveAppWindow(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { contains(aboveAppWindows, componentMatcher) }
-
-    /** {@inheritDoc} */
-    override fun containsBelowAppWindow(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { contains(belowAppWindows, componentMatcher) }
-
-    /** {@inheritDoc} */
-    override fun isAboveWindow(
-        aboveWindowComponentMatcher: IComponentMatcher,
-        belowWindowComponentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        contains(aboveWindowComponentMatcher)
-        contains(belowWindowComponentMatcher)
-
-        val aboveWindow =
-            wmState.windowStates.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it) }
-        val belowWindow =
-            wmState.windowStates.first { belowWindowComponentMatcher.windowMatchesAnyOf(it) }
-        if (aboveWindow == belowWindow) {
-            fail(
-                Fact(
-                    ASSERTION_TAG,
-                    "Above and below windows should be different. " +
-                        "Instead they were ${aboveWindow.title} " +
-                        "(matched with ${aboveWindowComponentMatcher.toWindowIdentifier()} and " +
-                        "${belowWindowComponentMatcher.toWindowIdentifier()})"
-                )
-            )
-        }
-
-        // windows are ordered by z-order, from top to bottom
-        val aboveZ =
-            wmState.windowStates.indexOfFirst { aboveWindowComponentMatcher.windowMatchesAnyOf(it) }
-        val belowZ =
-            wmState.windowStates.indexOfFirst { belowWindowComponentMatcher.windowMatchesAnyOf(it) }
-        if (aboveZ >= belowZ) {
-            val matchedAboveWindow =
-                subjects.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it.windowState) }
-            val aboveWindowTitleStr = aboveWindowComponentMatcher.toWindowIdentifier()
-            val belowWindowTitleStr = belowWindowComponentMatcher.toWindowIdentifier()
-            matchedAboveWindow.fail(
-                Fact(
-                    ASSERTION_TAG,
-                    "isAboveWindow(above=$aboveWindowTitleStr, below=$belowWindowTitleStr"
-                ),
-                Fact("Above", aboveWindowTitleStr),
-                Fact("Below", belowWindowTitleStr)
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun containsNonAppWindow(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { contains(nonAppWindows, componentMatcher) }
-
-    /** {@inheritDoc} */
-    override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
-        apply {
-            if (wmState.visibleAppWindows.isEmpty()) {
-                fail(
-                    Fact(
-                        ASSERTION_TAG,
-                        "isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})"
-                    ),
-                    Fact("Not found", "No visible app windows found")
-                )
-            }
-            val topVisibleAppWindow = wmState.topVisibleAppWindow
-            if (
-                topVisibleAppWindow == null ||
-                    !componentMatcher.windowMatchesAnyOf(topVisibleAppWindow)
-            ) {
-                isNotEmpty()
-                val topWindow = subjects.first { it.windowState == topVisibleAppWindow }
-                topWindow.fail(
-                    Fact(
-                        ASSERTION_TAG,
-                        "isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})"
-                    ),
-                    Fact("Not on top", componentMatcher.toWindowIdentifier()),
-                    Fact("Found", wmState.topVisibleAppWindow)
-                )
-            }
-        }
-
-    /** {@inheritDoc} */
-    override fun isAppWindowNotOnTop(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        val topVisibleAppWindow = wmState.topVisibleAppWindow
-        if (
-            topVisibleAppWindow != null && componentMatcher.windowMatchesAnyOf(topVisibleAppWindow)
-        ) {
-            val topWindow = subjects.first { it.windowState == topVisibleAppWindow }
-            topWindow.fail(
-                Fact(
-                    ASSERTION_TAG,
-                    "isAppWindowNotOnTop(${componentMatcher.toWindowIdentifier()})"
-                ),
-                Fact("On top", componentMatcher.toWindowIdentifier())
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun doNotOverlap(
-        vararg componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        check {
-                val repr = componentMatcher.joinToString(", ") { it.toWindowIdentifier() }
-                "Must give more than one window to check! (Given $repr)"
-            }
-            .that(componentMatcher.size)
-            .isEqual(1)
-
-        componentMatcher.forEach { contains(it) }
-        val foundWindows =
-            componentMatcher
-                .toSet()
-                .associateWith { act ->
-                    wmState.windowStates.firstOrNull { act.windowMatchesAnyOf(it) }
-                }
-                // keep entries only for windows that we actually found by removing nulls
-                .filterValues { it != null }
-        val foundWindowsRegions =
-            foundWindows.mapValues { (_, v) -> v?.frameRegion ?: Region.EMPTY }
-
-        val regions = foundWindowsRegions.entries.toList()
-        for (i in regions.indices) {
-            val (ourTitle, ourRegion) = regions[i]
-            for (j in i + 1 until regions.size) {
-                val (otherTitle, otherRegion) = regions[j]
-                if (ourRegion.op(otherRegion, Region.Op.INTERSECT)) {
-                    val window = foundWindows[ourTitle] ?: error("Window $ourTitle not found")
-                    val windowSubject = subjects.first { it.windowState == window }
-                    windowSubject.fail(
-                        Fact(
-                            ASSERTION_TAG,
-                            "noWindowsOverlap" +
-                                componentMatcher.joinToString { it.toWindowIdentifier() }
-                        ),
-                        Fact("Overlap", ourTitle),
-                        Fact("Overlap", otherTitle)
-                    )
-                }
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
-        apply {
-            // Check existence of activity
-            val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull()
-            check { "\"Activity for window ${componentMatcher.toWindowIdentifier()} exists" }
-                .that(activity)
-                .isNotNull()
-
-            // Check existence of window.
-            contains(componentMatcher)
-        }
-
-    /** {@inheritDoc} */
-    override fun hasRotation(
-        rotation: PlatformConsts.Rotation,
-        displayId: Int
-    ): WindowManagerStateSubject = apply {
-        check { "hasRotation" }.that(wmState.getRotation(displayId)).isEqual(rotation)
-    }
-
-    /** {@inheritDoc} */
-    override fun contains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply {
-        contains(subjects, componentMatcher)
-    }
-
-    /** {@inheritDoc} */
-    override fun notContainsAppWindow(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name
-        // nor an activity, ignore them
-        check { "Activity '${componentMatcher.toActivityIdentifier()}' does not exist" }
-            .that(wmState.containsActivity(componentMatcher))
-            .isEqual(false)
-        notContains(componentMatcher)
-    }
-
-    /** {@inheritDoc} */
-    override fun notContains(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
-        apply {
-            check { "Window '${componentMatcher.toWindowIdentifier()}' does not exist" }
-                .that(wmState.containsWindow(componentMatcher))
-                .isEqual(false)
-        }
-
-    /** {@inheritDoc} */
-    override fun isRecentsActivityVisible(): WindowManagerStateSubject = apply {
-        if (wmState.isHomeRecentsComponent) {
-            isHomeActivityVisible()
-        } else {
-            check { "Recents activity is visible" }
-                .that(wmState.isRecentsActivityVisible)
-                .isEqual(true)
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun isRecentsActivityInvisible(): WindowManagerStateSubject = apply {
-        if (wmState.isHomeRecentsComponent) {
-            isHomeActivityInvisible()
-        } else {
-            check { "Recents activity is not visible" }
-                .that(wmState.isRecentsActivityVisible)
-                .isEqual(false)
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun isValid(): WindowManagerStateSubject = apply {
-        check { "Stacks count" }.that(wmState.stackCount).isGreater(0)
-        // TODO: Update when keyguard will be shown on multiple displays
-        if (!wmState.keyguardControllerState.isKeyguardShowing) {
-            check { "Resumed activity" }.that(wmState.resumedActivitiesCount).isGreater(0)
-        }
-        check { "No focused activity" }.that(wmState.focusedActivity).isNotEqual(null)
-        wmState.rootTasks.forEach { aStack ->
-            val stackId = aStack.rootTaskId
-            aStack.tasks.forEach { aTask ->
-                check { "Root task Id for stack $aTask" }.that(stackId).isEqual(aTask.rootTaskId)
-            }
-        }
-        check { "Front window" }.that(wmState.frontWindow).isNotEqual(null)
-        check { "Focused window" }.that(wmState.focusedWindow).isNotEqual(null)
-        check { "Focused app" }.that(wmState.focusedApp.isNotEmpty()).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNonAppWindowVisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { checkWindowIsVisible(nonAppWindows, componentMatcher) }
-
-    /** {@inheritDoc} */
-    override fun isAppWindowVisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        containsAppWindow(componentMatcher)
-        checkWindowIsVisible(appWindows, componentMatcher)
-    }
-
-    /** {@inheritDoc} */
-    override fun hasNoVisibleAppWindow(): WindowManagerStateSubject = apply {
-        if (visibleAppWindows.isNotEmpty()) {
-            val visibleAppWindows = visibleAppWindows.joinToString { it.name }
-            fail(
-                Fact(ASSERTION_TAG, "hasNoVisibleAppWindow()"),
-                Fact("Found visible windows", visibleAppWindows)
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun isKeyguardShowing(): WindowManagerStateSubject = apply {
-        if (!wmState.isKeyguardShowing && !wmState.isAodShowing) {
-            fail(
-                Fact(ASSERTION_TAG, "isKeyguardShowing()"),
-                Fact("Keyguard showing", wmState.isKeyguardShowing)
-            )
-        }
-    }
-
-    /** {@inheritDoc} */
-    override fun isAppWindowInvisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { checkWindowIsInvisible(appWindows, componentMatcher) }
-
-    /** {@inheritDoc} */
-    override fun isNonAppWindowInvisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply { checkWindowIsInvisible(nonAppWindows, componentMatcher) }
-
-    private fun checkWindowIsVisible(
-        subjectList: List<WindowStateSubject>,
-        componentMatcher: IComponentMatcher
-    ) {
-        // Check existence of window.
-        contains(subjectList, componentMatcher)
-
-        val foundWindows =
-            subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
-
-        val visibleWindows =
-            wmState.visibleWindows.filter { visibleWindow ->
-                foundWindows.any { it.windowState == visibleWindow }
-            }
-
-        if (visibleWindows.isEmpty()) {
-            val windowId = componentMatcher.toWindowIdentifier()
-            val facts =
-                listOf(Fact(ASSERTION_TAG, "isVisible($windowId)"), Fact("Is Invisible", windowId))
-            foundWindows.first().fail(facts)
-        }
-    }
-
-    private fun checkWindowIsInvisible(
-        subjectList: List<WindowStateSubject>,
-        componentMatcher: IComponentMatcher
-    ) {
-        // Check existence of window.
-        contains(subjectList, componentMatcher)
-
-        val foundWindows =
-            subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) }
-
-        val visibleWindows =
-            wmState.visibleWindows.filter { visibleWindow ->
-                foundWindows.any { it.windowState == visibleWindow }
-            }
-
-        if (visibleWindows.isNotEmpty()) {
-            val windowId = componentMatcher.toWindowIdentifier()
-            val facts =
-                listOf(Fact(ASSERTION_TAG, "isInvisible($windowId)"), Fact("Is Visible", windowId))
-            foundWindows.first { it.windowState == visibleWindows.first() }.fail(facts)
-        }
-    }
-
-    private fun contains(
-        subjectList: List<WindowStateSubject>,
-        componentMatcher: IComponentMatcher
-    ) {
-        val windowStates = subjectList.map { it.windowState }
-        check { "Window '${componentMatcher.toWindowIdentifier()}' exists" }
-            .that(componentMatcher.windowMatchesAnyOf(windowStates))
-            .isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isHomeActivityVisible(): WindowManagerStateSubject = apply {
-        val homeIsVisible = wmState.homeActivity?.isVisible ?: false
-        check { "Home activity exists" }.that(wmState.homeActivity).isNotEqual(null)
-        check { "Home activity is visible" }.that(homeIsVisible).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isHomeActivityInvisible(): WindowManagerStateSubject = apply {
-        val homeIsVisible = wmState.homeActivity?.isVisible ?: false
-        check { "Home activity is not visible" }.that(homeIsVisible).isEqual(false)
-    }
-
-    /** {@inheritDoc} */
-    override fun isFocusedApp(app: String): WindowManagerStateSubject = apply {
-        check { "Window is focused app $app" }.that(wmState.focusedApp).isEqual(app)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotFocusedApp(app: String): WindowManagerStateSubject = apply {
-        check { "Window is not focused app $app" }.that(wmState.focusedApp).isNotEqual(app)
-    }
-
-    /** {@inheritDoc} */
-    override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply {
-        contains(componentMatcher)
-        check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" }
-            .that(wmState.isInPipMode(componentMatcher))
-            .isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject =
-        apply {
-            contains(componentMatcher)
-            check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" }
-                .that(wmState.isInPipMode(componentMatcher))
-                .isEqual(false)
-        }
-
-    /** {@inheritDoc} */
-    override fun isAppSnapshotStartingWindowVisibleFor(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject = apply {
-        val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull()
-        requireNotNull(activity) { "Activity for $componentMatcher not found" }
-
-        // Check existence and visibility of SnapshotStartingWindow
-        val snapshotStartingWindow =
-            activity.getWindows(ComponentNameMatcher.SNAPSHOT).firstOrNull()
-
-        check { "SnapshotStartingWindow does not exist for activity ${activity.name}" }
-            .that(snapshotStartingWindow)
-            .isNotEqual(null)
-        check { "Activity is visible" }.that(activity.isVisible).isEqual(true)
-        check { "SnapshotStartingWindow is visible" }
-            .that(snapshotStartingWindow?.isVisible ?: false)
-            .isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isAboveAppWindowVisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject =
-        containsAboveAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher)
-
-    /** {@inheritDoc} */
-    override fun isAboveAppWindowInvisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject =
-        containsAboveAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher)
-
-    /** {@inheritDoc} */
-    override fun isBelowAppWindowVisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject =
-        containsBelowAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher)
-
-    /** {@inheritDoc} */
-    override fun isBelowAppWindowInvisible(
-        componentMatcher: IComponentMatcher
-    ): WindowManagerStateSubject =
-        containsBelowAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher)
-
-    /** Obtains the first subject with [WindowState.title] containing [name]. */
-    fun windowState(name: String): WindowStateSubject? = windowState { it.name.contains(name) }
-
-    /**
-     * Obtains the first subject matching [predicate].
-     *
-     * @param predicate to search for a subject
-     */
-    fun windowState(predicate: (WindowState) -> Boolean): WindowStateSubject? =
-        subjects.firstOrNull { predicate(it.windowState) }
-
-    override fun toString(): String {
-        return "WindowManagerStateSubject($wmState)"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerTraceSubject.kt
deleted file mode 100644
index dbbccc8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowManagerTraceSubject.kt
+++ /dev/null
@@ -1,578 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.wm
-
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.region.RegionTrace
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-import com.android.server.wm.traces.common.subjects.region.RegionTraceSubject
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.common.windowmanager.windows.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.isEmpty()).isEqual(true)
-    }
-
-    /** {@inheritDoc} */
-    override fun isNotEmpty(): WindowManagerTraceSubject = apply {
-        check { "Trace is not empty" }.that(trace.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: PlatformConsts.Rotation,
-        displayId: Int
-    ): WindowManagerTraceSubject = hasRotation(rotation, displayId, isOptional = false)
-
-    /** See [hasRotation] */
-    fun hasRotation(
-        rotation: PlatformConsts.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 }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowStateSubject.kt b/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowStateSubject.kt
deleted file mode 100644
index f74737f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/subjects/wm/WindowStateSubject.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.subjects.wm
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-
-/**
- * Subject for [WindowState] objects, used to make assertions over behaviors that occur on a single
- * [WindowState] of a WM state.
- *
- * To make assertions over a layer from a state it is recommended to create a subject using
- * [WindowManagerStateSubject.windowState](windowStateName)
- *
- * Alternatively, it is also possible to use [WindowStateSubject](myWindow).
- *
- * Example:
- * ```
- *    val trace = WindowManagerTraceParser().parse(myTraceFile)
- *    val subject = WindowManagerTraceSubject(trace).first()
- *        .windowState("ValidWindow")
- *        .exists()
- *        { myCustomAssertion(this) }
- * ```
- */
-class WindowStateSubject(
-    override val parent: WindowManagerStateSubject,
-    override val timestamp: Timestamp,
-    val windowState: WindowState
-) : FlickerSubject() {
-    val isVisible: Boolean = windowState.isVisible
-    val isInvisible: Boolean = !windowState.isVisible
-    val name: String = windowState.name
-    val frame: RegionSubject
-        get() = RegionSubject(windowState.frame, this, timestamp)
-
-    override val selfFacts = listOf(Fact("Window title", windowState.title))
-
-    /** If the [windowState] exists, executes a custom [assertion] on the current subject */
-    operator fun invoke(assertion: (WindowState) -> Unit): WindowStateSubject = apply {
-        assertion(this.windowState)
-    }
-
-    override fun toString(): String {
-        return "WindowState:$name"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/Transaction.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transactions/Transaction.kt
deleted file mode 100644
index 98ac94e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/Transaction.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.traces.common.transactions
-
-import kotlin.js.JsName
-
-data class Transaction(
-    @JsName("pid") val pid: Int,
-    @JsName("uid") val uid: Int,
-    @JsName("requestedVSyncId") val requestedVSyncId: Long,
-    @JsName("postTime") val postTime: Long,
-    @JsName("id") val id: Long,
-) {
-    constructor(
-        pid: Int,
-        uid: Int,
-        requestedVSyncId: Long,
-        appliedInEntry: TransactionsTraceEntry,
-        postTime: Long,
-        id: Long
-    ) : this(pid, uid, requestedVSyncId, postTime, id) {
-        this.appliedInEntry = appliedInEntry
-    }
-
-    lateinit var appliedInEntry: TransactionsTraceEntry
-        internal set
-
-    val appliedVSyncId: Long
-        get() = appliedInEntry.vSyncId
-
-    override fun toString(): String {
-        return "Transaction#${hashCode().toString(16)}" +
-            "(pid=$pid, uid=$uid, requestedVSyncId=$requestedVSyncId, postTime=$postTime, " +
-            "id=$id)"
-    }
-
-    companion object {
-        @JsName("emptyTransaction")
-        fun emptyTransaction(): Transaction {
-            return Transaction(0, 0, 0, 0, 0)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTrace.kt
deleted file mode 100644
index 2cd6873..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTrace.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.traces.common.transactions
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-class TransactionsTrace(override val entries: Array<TransactionsTraceEntry>) :
-    ITrace<TransactionsTraceEntry>, List<TransactionsTraceEntry> by entries.toList() {
-
-    init {
-        val alwaysIncreasing =
-            entries
-                .toList()
-                .zipWithNext { prev, next ->
-                    prev.timestamp.elapsedNanos < next.timestamp.elapsedNanos
-                }
-                .all { it }
-        require(alwaysIncreasing) { "Transaction timestamp not always increasing..." }
-    }
-
-    @JsName("allTransactions")
-    val allTransactions: List<Transaction> = entries.toList().flatMap { it.transactions.toList() }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): TransactionsTrace {
-        return TransactionsTrace(
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTraceEntry.kt
deleted file mode 100644
index 591a5df..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transactions/TransactionsTraceEntry.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.traces.common.transactions
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-class TransactionsTraceEntry(
-    override val timestamp: Timestamp,
-    @JsName("vSyncId") val vSyncId: Long,
-    @JsName("transactions") val transactions: Array<Transaction>
-) : ITraceEntry
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitMode.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitMode.kt
deleted file mode 100644
index 8e02c42..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitMode.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * 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 com.android.server.wm.traces.common.transition
-
-import kotlin.js.JsName
-
-enum class TransitMode {
-    TRANSIT_NONE,
-    TRANSIT_OPEN,
-    TRANSIT_CLOSE,
-    TRANSIT_TO_FRONT,
-    TRANSIT_TO_BACK,
-    TRANSIT_CHANGE;
-
-    companion object {
-        @JsName("fromInt") fun fromInt(value: Int) = values().first { it.ordinal == value }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/Transition.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/Transition.kt
deleted file mode 100644
index a882adc..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/Transition.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.traces.common.transition
-
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.transactions.Transaction
-import kotlin.js.JsName
-
-class Transition(
-    @JsName("start") val start: Timestamp,
-    @JsName("sendTime") val sendTime: Timestamp,
-    @JsName("startTransactionId") val startTransactionId: Long,
-    @JsName("finishTransactionId") val finishTransactionId: Long,
-    @JsName("changes") val changes: List<TransitionChange>,
-    @JsName("played") val played: Boolean,
-    @JsName("aborted") val aborted: Boolean
-) : ITraceEntry {
-    override val timestamp = start
-
-    @JsName("startTransaction") val startTransaction: Transaction? = null // TODO: Get
-    @JsName("finishTransaction") val finishTransaction: Transaction? = null // TODO: Get
-
-    @JsName("isIncomplete")
-    val isIncomplete: Boolean
-        get() = !played || aborted
-
-    override fun toString(): String =
-        "Transition#${hashCode()}" +
-            "(\naborted=$aborted,\nstart=$start,\nsendTime=$sendTime,\n" +
-            "startTransaction=$startTransaction,\nfinishTransaction=$finishTransaction,\n" +
-            "changes=[\n${changes.joinToString(",\n").prependIndent()}\n])"
-
-    companion object {
-        enum class Type(val value: Int) {
-            UNDEFINED(-1),
-            NONE(0),
-            OPEN(1),
-            CLOSE(2),
-            TO_FRONT(3),
-            TO_BACK(4),
-            RELAUNCH(5),
-            CHANGE(6),
-            KEYGUARD_GOING_AWAY(7),
-            KEYGUARD_OCCLUDE(8),
-            KEYGUARD_UNOCCLUDE(9),
-            PIP(10),
-            WAKE(11),
-            // START OF CUSTOM TYPES
-            FIRST_CUSTOM(12); // TODO: Add custom types we know about
-
-            companion object {
-                @JsName("fromInt") fun fromInt(value: Int) = values().first { it.value == value }
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionChange.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionChange.kt
deleted file mode 100644
index e2cb2bd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionChange.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.traces.common.transition
-
-import com.android.server.wm.traces.common.WindowingMode
-import com.android.server.wm.traces.common.transition.Transition.Companion.Type
-import kotlin.js.JsName
-
-class TransitionChange(
-    @JsName("transitMode") val transitMode: Type,
-    @JsName("layerId") val layerId: Int,
-    @JsName("windowId") val windowId: Int,
-    @JsName("windowingMode") val windowingMode: WindowingMode
-) {
-
-    override fun toString(): String {
-        return "TransitionChange(" +
-            "transitMode=$transitMode, " +
-            "layerId=$layerId, " +
-            "windowId=$windowId, " +
-            "windowingMode=$windowingMode" +
-            ")"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionInfo.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionInfo.kt
deleted file mode 100644
index 4db7439..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionInfo.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.transition
-
-data class TransitionInfo(val transitionId: Int, val changes: List<Change>) {
-    data class Change(
-        val layerId: Int,
-        val transitMode: TransitMode,
-    )
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionState.kt
deleted file mode 100644
index f1db925..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionState.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.traces.common.transition
-
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-
-data class TransitionState(
-    @JsName("id") val id: Int,
-    @JsName("type") val type: Transition.Companion.Type,
-    @JsName("timestamp") val timestamp: Timestamp,
-    @JsName("state") val state: State,
-    @JsName("flags") val flags: Int,
-    @JsName("changes") val changes: List<TransitionChange>,
-    @JsName("startTransactionId") val startTransactionId: Long,
-    @JsName("finishTransactionId") val finishTransactionId: Long
-) {
-    companion object {
-        enum class State(val value: Int) {
-            PENDING(-1),
-            COLLECTING(0),
-            STARTED(1),
-            PLAYING(2),
-            ABORT(3),
-            FINISHED(4);
-
-            companion object {
-                @JsName("fromInt") fun fromInt(value: Int) = values().first { it.value == value }
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionTraceEntry.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionTraceEntry.kt
deleted file mode 100644
index 3fd82bf..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionTraceEntry.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 com.android.server.wm.traces.common.transition
-
-class TransitionTraceEntry
-private constructor(
-    val transitionState: TransitionState? = null,
-    val transitionInfo: TransitionInfo? = null
-) {
-    fun hasTransitionState(): Boolean = transitionState != null
-    fun hasTransitionInfo(): Boolean = transitionInfo != null
-
-    constructor(transitionState: TransitionState) : this(transitionState, null)
-
-    constructor(transitionInfo: TransitionInfo) : this(null, transitionInfo)
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionsTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionsTrace.kt
deleted file mode 100644
index 9e9441a..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/transition/TransitionsTrace.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.traces.common.transition
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import kotlin.js.JsName
-import kotlin.text.StringBuilder
-
-data class TransitionsTrace(override val entries: Array<Transition>) :
-    ITrace<Transition>, List<Transition> by entries.toList() {
-    constructor(entry: Transition) : this(arrayOf(entry))
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is TransitionsTrace) return false
-
-        if (!entries.contentEquals(other.entries)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        return entries.contentHashCode()
-    }
-
-    @JsName("prettyPrint")
-    fun prettyPrint(): String {
-        val sb = StringBuilder("TransitionTrace(")
-
-        for (transition in entries) {
-            sb.append("\n\t- ").append(transition)
-        }
-        if (entries.isEmpty()) {
-            sb.append("EMPTY)")
-        } else {
-            sb.append("\n)")
-        }
-        return sb.toString()
-    }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): TransitionsTrace {
-        require(startTimestamp.hasElapsedTimestamp && endTimestamp.hasElapsedTimestamp)
-        return sliceElapsed(startTimestamp.elapsedNanos, endTimestamp.elapsedNanos)
-    }
-
-    private fun sliceElapsed(from: Long, to: Long): TransitionsTrace {
-        return TransitionsTrace(
-            this.entries
-                .dropWhile { it.sendTime.elapsedNanos < from }
-                .dropLastWhile { it.start.elapsedNanos > to }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt
deleted file mode 100644
index 0c91911..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerState.kt
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.windowmanager
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.Task
-import com.android.server.wm.traces.common.windowmanager.windows.TaskFragment
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import kotlin.js.JsName
-
-/**
- * Represents a single WindowManager trace entry.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- *
- * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
- */
-class WindowManagerState(
-    @JsName("elapsedTimestamp") val elapsedTimestamp: Long,
-    @JsName("clockTimestamp") val clockTimestamp: Long?,
-    @JsName("where") val where: String,
-    @JsName("policy") val policy: WindowManagerPolicy?,
-    @JsName("focusedApp") val focusedApp: String,
-    @JsName("focusedDisplayId") val focusedDisplayId: Int,
-    @JsName("_focusedWindow") private val _focusedWindow: String,
-    @JsName("inputMethodWindowAppToken") val inputMethodWindowAppToken: String,
-    @JsName("isHomeRecentsComponent") val isHomeRecentsComponent: Boolean,
-    @JsName("isDisplayFrozen") val isDisplayFrozen: Boolean,
-    @JsName("_pendingActivities") private val _pendingActivities: Array<String>,
-    @JsName("root") val root: RootWindowContainer,
-    @JsName("keyguardControllerState") val keyguardControllerState: KeyguardControllerState
-) : ITraceEntry {
-    override val timestamp =
-        CrossPlatform.timestamp.from(elapsedNanos = elapsedTimestamp, unixNanos = clockTimestamp)
-    @JsName("isVisible") val isVisible: Boolean = true
-    @JsName("stableId")
-    val stableId: String
-        get() = this::class.simpleName ?: error("Unable to determine class")
-    @JsName("isTablet")
-    val isTablet: Boolean
-        get() = displays.any { it.isTablet }
-
-    @JsName("windowContainers")
-    val windowContainers: Array<WindowContainer>
-        get() = root.collectDescendants()
-
-    @JsName("children")
-    val children: Array<WindowContainer>
-        get() = root.children.reversedArray()
-
-    /** Displays in z-order with the top most at the front of the list, starting with primary. */
-    @JsName("displays")
-    val displays: Array<DisplayContent>
-        get() = windowContainers.filterIsInstance<DisplayContent>().toTypedArray()
-
-    /**
-     * Root tasks in z-order with the top most at the front of the list, starting with primary
-     * display.
-     */
-    @JsName("rootTasks")
-    val rootTasks: Array<Task>
-        get() = displays.flatMap { it.rootTasks.toList() }.toTypedArray()
-
-    /** TaskFragments in z-order with the top most at the front of the list. */
-    @JsName("taskFragments")
-    val taskFragments: Array<TaskFragment>
-        get() = windowContainers.filterIsInstance<TaskFragment>().toTypedArray()
-
-    /** Windows in z-order with the top most at the front of the list. */
-    @JsName("windowStates")
-    val windowStates: Array<WindowState>
-        get() = windowContainers.filterIsInstance<WindowState>().toTypedArray()
-
-    @Deprecated("Please use windowStates instead", replaceWith = ReplaceWith("windowStates"))
-    @JsName("windows")
-    val windows: Array<WindowState>
-        get() = windowStates
-
-    @JsName("appWindows")
-    val appWindows: Array<WindowState>
-        get() = windowStates.filter { it.isAppWindow }.toTypedArray()
-    @JsName("nonAppWindows")
-    val nonAppWindows: Array<WindowState>
-        get() = windowStates.filterNot { it.isAppWindow }.toTypedArray()
-    @JsName("aboveAppWindows")
-    val aboveAppWindows: Array<WindowState>
-        get() = windowStates.takeWhile { !appWindows.contains(it) }.toTypedArray()
-    @JsName("belowAppWindows")
-    val belowAppWindows: Array<WindowState>
-        get() =
-            windowStates.dropWhile { !appWindows.contains(it) }.drop(appWindows.size).toTypedArray()
-    @JsName("visibleWindows")
-    val visibleWindows: Array<WindowState>
-        get() =
-            windowStates
-                .filter {
-                    val activities = getActivitiesForWindowState(it)
-                    val windowIsVisible = it.isVisible
-                    val activityIsVisible = activities.any { activity -> activity.isVisible }
-
-                    // for invisible checks it suffices if activity or window is invisible
-                    windowIsVisible && (activityIsVisible || activities.isEmpty())
-                }
-                .toTypedArray()
-    @JsName("visibleAppWindows")
-    val visibleAppWindows: Array<WindowState>
-        get() = visibleWindows.filter { it.isAppWindow }.toTypedArray()
-    @JsName("topVisibleAppWindow")
-    val topVisibleAppWindow: WindowState?
-        get() = visibleAppWindows.firstOrNull()
-    @JsName("pinnedWindows")
-    val pinnedWindows: Array<WindowState>
-        get() = visibleWindows.filter { it.windowingMode == WINDOWING_MODE_PINNED }.toTypedArray()
-    @JsName("pendingActivities")
-    val pendingActivities: Array<Activity>
-        get() = _pendingActivities.mapNotNull { getActivityByName(it) }.toTypedArray()
-    @JsName("focusedWindow")
-    val focusedWindow: WindowState?
-        get() = visibleWindows.firstOrNull { it.name == _focusedWindow }
-
-    val isKeyguardShowing: Boolean
-        get() = keyguardControllerState.isKeyguardShowing
-    val isAodShowing: Boolean
-        get() = keyguardControllerState.isAodShowing
-    /**
-     * Checks if the device state supports rotation, i.e., if the rotation sensor is enabled (e.g.,
-     * launcher) and if the rotation not fixed
-     */
-    @JsName("canRotate")
-    val canRotate: Boolean
-        get() = policy?.isFixedOrientation != true && policy?.isOrientationNoSensor != true
-    @JsName("focusedDisplay")
-    val focusedDisplay: DisplayContent?
-        get() = getDisplay(focusedDisplayId)
-    @JsName("focusedStackId")
-    val focusedStackId: Int
-        get() = focusedDisplay?.focusedRootTaskId ?: -1
-    @JsName("focusedActivity")
-    val focusedActivity: Activity?
-        get() {
-            val focusedDisplay = focusedDisplay
-            val focusedWindow = focusedWindow
-            return when {
-                focusedDisplay != null && focusedDisplay.resumedActivity.isNotEmpty() ->
-                    getActivityByName(focusedDisplay.resumedActivity)
-                focusedWindow != null ->
-                    getActivitiesForWindowState(focusedWindow, focusedDisplayId).firstOrNull()
-                else -> null
-            }
-        }
-    @JsName("resumedActivities")
-    val resumedActivities: Array<Activity>
-        get() =
-            rootTasks
-                .flatMap { it.resumedActivities.toList() }
-                .mapNotNull { getActivityByName(it) }
-                .toTypedArray()
-    @JsName("resumedActivitiesCount")
-    val resumedActivitiesCount: Int
-        get() = resumedActivities.size
-    @JsName("stackCount")
-    val stackCount: Int
-        get() = rootTasks.size
-    @JsName("homeTask")
-    val homeTask: Task?
-        get() = getStackByActivityType(ACTIVITY_TYPE_HOME)?.topTask
-    @JsName("recentsTask")
-    val recentsTask: Task?
-        get() = getStackByActivityType(ACTIVITY_TYPE_RECENTS)?.topTask
-    @JsName("homeActivity")
-    val homeActivity: Activity?
-        get() = homeTask?.activities?.lastOrNull()
-    @JsName("isHomeActivityVisible")
-    val isHomeActivityVisible: Boolean
-        get() {
-            val activity = homeActivity
-            return activity != null && activity.isVisible
-        }
-    @JsName("recentsActivity")
-    val recentsActivity: Activity?
-        get() = recentsTask?.activities?.lastOrNull()
-    @JsName("isRecentsActivityVisible")
-    val isRecentsActivityVisible: Boolean
-        get() {
-            val activity = recentsActivity
-            return activity != null && activity.isVisible
-        }
-    @JsName("frontWindow")
-    val frontWindow: WindowState?
-        get() = windowStates.firstOrNull()
-    @JsName("inputMethodWindowState")
-    val inputMethodWindowState: WindowState?
-        get() = getWindowStateForAppToken(inputMethodWindowAppToken)
-
-    @JsName("getDefaultDisplay")
-    fun getDefaultDisplay(): DisplayContent? = displays.firstOrNull { it.id == DEFAULT_DISPLAY }
-
-    @JsName("getDisplay")
-    fun getDisplay(displayId: Int): DisplayContent? = displays.firstOrNull { it.id == displayId }
-
-    @JsName("countStacks")
-    fun countStacks(windowingMode: Int, activityType: Int): Int {
-        var count = 0
-        for (stack in rootTasks) {
-            if (activityType != ACTIVITY_TYPE_UNDEFINED && activityType != stack.activityType) {
-                continue
-            }
-            if (windowingMode != WINDOWING_MODE_UNDEFINED && windowingMode != stack.windowingMode) {
-                continue
-            }
-            ++count
-        }
-        return count
-    }
-
-    @JsName("getRootTask")
-    fun getRootTask(taskId: Int): Task? = rootTasks.firstOrNull { it.rootTaskId == taskId }
-
-    @JsName("getRotation")
-    fun getRotation(displayId: Int): PlatformConsts.Rotation =
-        getDisplay(displayId)?.rotation ?: error("Default display not found")
-
-    @JsName("getOrientation")
-    fun getOrientation(displayId: Int): Int =
-        getDisplay(displayId)?.lastOrientation ?: error("Default display not found")
-
-    @JsName("getStackByActivityType")
-    fun getStackByActivityType(activityType: Int): Task? =
-        rootTasks.firstOrNull { it.activityType == activityType }
-
-    @JsName("getStandardStackByWindowingMode")
-    fun getStandardStackByWindowingMode(windowingMode: Int): Task? =
-        rootTasks.firstOrNull {
-            it.activityType == ACTIVITY_TYPE_STANDARD && it.windowingMode == windowingMode
-        }
-
-    @JsName("getActivitiesForWindowState")
-    fun getActivitiesForWindowState(
-        windowState: WindowState,
-        displayId: Int = DEFAULT_DISPLAY
-    ): List<Activity> {
-        return displays
-            .firstOrNull { it.id == displayId }
-            ?.rootTasks
-            ?.mapNotNull { stack ->
-                stack.getActivity { activity -> activity.hasWindowState(windowState) }
-            }
-            ?: emptyList()
-    }
-
-    /**
-     * Get the all activities on display with id [displayId], containing a matching
-     * [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     * @param displayId display where to search the activity
-     */
-    @JsName("getActivitiesForWindow")
-    fun getActivitiesForWindow(
-        componentMatcher: IComponentMatcher,
-        displayId: Int = DEFAULT_DISPLAY
-    ): List<Activity> {
-        return displays
-            .firstOrNull { it.id == displayId }
-            ?.rootTasks
-            ?.mapNotNull { stack ->
-                stack.getActivity { activity -> activity.hasWindow(componentMatcher) }
-            }
-            ?: emptyList()
-    }
-
-    /**
-     * @return if any activity matches [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("containsActivity")
-    fun containsActivity(componentMatcher: IComponentMatcher): Boolean =
-        rootTasks.any { it.containsActivity(componentMatcher) }
-
-    /**
-     * @return the first [Activity] matching [componentMatcher], or null otherwise
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getActivity")
-    fun getActivity(componentMatcher: IComponentMatcher): Activity? =
-        rootTasks.firstNotNullOfOrNull { it.getActivity(componentMatcher) }
-
-    @JsName("getActivityByName")
-    private fun getActivityByName(activityName: String): Activity? =
-        rootTasks.firstNotNullOfOrNull { task ->
-            task.getActivity { activity -> activity.title.contains(activityName) }
-        }
-
-    /**
-     * @return if any activity matching [componentMatcher] is visible
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("isActivityVisible")
-    fun isActivityVisible(componentMatcher: IComponentMatcher): Boolean =
-        getActivity(componentMatcher)?.isVisible ?: false
-
-    /**
-     * @return if any activity matching [componentMatcher] has state of [activityState]
-     *
-     * @param componentMatcher Components to search
-     * @param activityState expected activity state
-     */
-    @JsName("hasActivityState")
-    fun hasActivityState(componentMatcher: IComponentMatcher, activityState: String): Boolean =
-        rootTasks.any { it.getActivity(componentMatcher)?.state == activityState }
-
-    /**
-     * @return if any pending activities match [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("pendingActivityContain")
-    fun pendingActivityContain(componentMatcher: IComponentMatcher): Boolean =
-        componentMatcher.activityMatchesAnyOf(pendingActivities)
-
-    /**
-     * @return the visible [WindowState]s matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getMatchingVisibleWindowState")
-    fun getMatchingVisibleWindowState(componentMatcher: IComponentMatcher): Array<WindowState> {
-        return windowStates
-            .filter { it.isSurfaceShown && componentMatcher.windowMatchesAnyOf(it) }
-            .toTypedArray()
-    }
-
-    /** @return the [WindowState] for the nav bar in the display with id [displayId] */
-    @JsName("getNavBarWindow")
-    fun getNavBarWindow(displayId: Int): WindowState? {
-        val navWindow = windowStates.filter { it.isValidNavBarType && it.displayId == displayId }
-
-        // We may need some time to wait for nav bar showing.
-        // It's Ok to get 0 nav bar here.
-        if (navWindow.size > 1) {
-            throw IllegalStateException("There should be at most one navigation bar on a display")
-        }
-        return navWindow.firstOrNull()
-    }
-
-    @JsName("getWindowStateForAppToken")
-    private fun getWindowStateForAppToken(appToken: String): WindowState? =
-        windowStates.firstOrNull { it.token == appToken }
-
-    /**
-     * Checks if there exists a [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("containsWindow")
-    fun containsWindow(componentMatcher: IComponentMatcher): Boolean =
-        componentMatcher.windowMatchesAnyOf(windowStates.asList())
-
-    /**
-     * Check if at least one [WindowState] matching [componentMatcher] is visible
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("isWindowSurfaceShown")
-    fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Boolean =
-        getMatchingVisibleWindowState(componentMatcher).isNotEmpty()
-
-    /** Checks if the state has any window in PIP mode */
-    @JsName("hasPipWindow") fun hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
-
-    /**
-     * Checks that a [WindowState] matching [componentMatcher] is in PIP mode
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("isInPipMode")
-    fun isInPipMode(componentMatcher: IComponentMatcher): Boolean =
-        componentMatcher.windowMatchesAnyOf(pinnedWindows.asList())
-
-    @JsName("getZOrder")
-    fun getZOrder(w: WindowState): Int = windowStates.size - windowStates.indexOf(w)
-
-    @JsName("defaultMinimalTaskSize")
-    fun defaultMinimalTaskSize(displayId: Int): Int =
-        dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP.toFloat(), getDisplay(displayId)!!.dpi)
-
-    @JsName("defaultMinimalDisplaySizeForSplitScreen")
-    fun defaultMinimalDisplaySizeForSplitScreen(displayId: Int): Int {
-        return dpToPx(
-            DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP.toFloat(),
-            getDisplay(displayId)!!.dpi
-        )
-    }
-
-    @JsName("getIsIncompleteReason")
-    fun getIsIncompleteReason(): String {
-        return buildString {
-            if (rootTasks.isEmpty()) {
-                append("No stacks found...")
-            }
-            if (focusedStackId == -1) {
-                append("No focused stack found...")
-            }
-            if (focusedActivity == null) {
-                append("No focused activity found...")
-            }
-            if (resumedActivities.isEmpty()) {
-                append("No resumed activities found...")
-            }
-            if (windowStates.isEmpty()) {
-                append("No Windows found...")
-            }
-            if (focusedWindow == null) {
-                append("No Focused Window...")
-            }
-            if (focusedApp.isEmpty()) {
-                append("No Focused App...")
-            }
-            if (keyguardControllerState.isKeyguardShowing) {
-                append("Keyguard showing...")
-            }
-        }
-    }
-
-    @JsName("isComplete") fun isComplete(): Boolean = !isIncomplete()
-    @JsName("isIncomplete")
-    fun isIncomplete(): Boolean {
-        return rootTasks.isEmpty() ||
-            focusedStackId == -1 ||
-            windowStates.isEmpty() ||
-            // overview screen has no focused window
-            ((focusedApp.isEmpty() || focusedWindow == null) && homeActivity == null) ||
-            (focusedActivity == null || resumedActivities.isEmpty()) &&
-                !keyguardControllerState.isKeyguardShowing
-    }
-
-    @JsName("asTrace") fun asTrace(): WindowManagerTrace = WindowManagerTrace(arrayOf(this))
-
-    override fun toString(): String {
-        return "${timestamp}ns"
-    }
-
-    companion object {
-        @JsName("STATE_INITIALIZING") const val STATE_INITIALIZING = "INITIALIZING"
-        @JsName("STATE_RESUMED") const val STATE_RESUMED = "RESUMED"
-        @JsName("STATE_PAUSED") const val STATE_PAUSED = "PAUSED"
-        @JsName("STATE_STOPPED") const val STATE_STOPPED = "STOPPED"
-        @JsName("STATE_DESTROYED") const val STATE_DESTROYED = "DESTROYED"
-        @JsName("APP_STATE_IDLE") const val APP_STATE_IDLE = "APP_STATE_IDLE"
-        @JsName("ACTIVITY_TYPE_UNDEFINED") internal const val ACTIVITY_TYPE_UNDEFINED = 0
-        @JsName("ACTIVITY_TYPE_STANDARD") internal const val ACTIVITY_TYPE_STANDARD = 1
-        @JsName("DEFAULT_DISPLAY") internal const val DEFAULT_DISPLAY = 0
-        @JsName("DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP")
-        internal const val DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440
-        @JsName("ACTIVITY_TYPE_HOME") internal const val ACTIVITY_TYPE_HOME = 2
-        @JsName("ACTIVITY_TYPE_RECENTS") internal const val ACTIVITY_TYPE_RECENTS = 3
-        @JsName("WINDOWING_MODE_UNDEFINED") internal const val WINDOWING_MODE_UNDEFINED = 0
-        @JsName("DENSITY_DEFAULT") private const val DENSITY_DEFAULT = 160
-        /** @see android.app.WindowConfiguration.WINDOWING_MODE_PINNED */
-        @JsName("WINDOWING_MODE_PINNED") private const val WINDOWING_MODE_PINNED = 2
-
-        /** @see android.view.WindowManager.LayoutParams */
-        @JsName("TYPE_NAVIGATION_BAR_PANEL") internal const val TYPE_NAVIGATION_BAR_PANEL = 2024
-
-        // Default minimal size of resizable task, used if none is set explicitly.
-        // Must be kept in sync with 'default_minimal_size_resizable_task'
-        // dimen from frameworks/base.
-        @JsName("DEFAULT_RESIZABLE_TASK_SIZE_DP")
-        internal const val DEFAULT_RESIZABLE_TASK_SIZE_DP = 220
-
-        @JsName("dpToPx")
-        fun dpToPx(dp: Float, densityDpi: Int): Int {
-            return (dp * densityDpi / DENSITY_DEFAULT + 0.5f).toInt()
-        }
-    }
-    override fun equals(other: Any?): Boolean {
-        return other is WindowManagerState && other.timestamp == this.timestamp
-    }
-
-    override fun hashCode(): Int {
-        var result = where.hashCode()
-        result = 31 * result + (policy?.hashCode() ?: 0)
-        result = 31 * result + focusedApp.hashCode()
-        result = 31 * result + focusedDisplayId
-        result = 31 * result + focusedWindow.hashCode()
-        result = 31 * result + inputMethodWindowAppToken.hashCode()
-        result = 31 * result + isHomeRecentsComponent.hashCode()
-        result = 31 * result + isDisplayFrozen.hashCode()
-        result = 31 * result + pendingActivities.contentHashCode()
-        result = 31 * result + root.hashCode()
-        result = 31 * result + keyguardControllerState.hashCode()
-        result = 31 * result + timestamp.hashCode()
-        result = 31 * result + isVisible.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt
deleted file mode 100644
index ad3c37f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTrace.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.windowmanager
-
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.service.PlatformConsts
-import kotlin.js.JsName
-
-/**
- * Contains a collection of parsed WindowManager trace entries and assertions to apply over a single
- * entry.
- *
- * Each entry is parsed into a list of [WindowManagerState] objects.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-data class WindowManagerTrace(override val entries: Array<WindowManagerState>) :
-    ITrace<WindowManagerState>, List<WindowManagerState> by entries.toList() {
-
-    @JsName("isTablet")
-    val isTablet: Boolean
-        get() = entries.any { it.isTablet }
-
-    override fun toString(): String {
-        return "WindowManagerTrace(Start: ${entries.firstOrNull()}, " +
-            "End: ${entries.lastOrNull()})"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowManagerTrace) return false
-
-        if (!entries.contentEquals(other.entries)) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        return entries.contentHashCode()
-    }
-
-    /** Get the initial rotation */
-    fun getInitialRotation(): PlatformConsts.Rotation {
-        if (entries.isEmpty()) {
-            throw RuntimeException("WindowManager Trace has no entries")
-        }
-        val firstWmState = entries[0]
-        return firstWmState.policy?.rotation
-            ?: run { throw RuntimeException("Wm state has no policy") }
-    }
-
-    /** Get the final rotation */
-    fun getFinalRotation(): PlatformConsts.Rotation {
-        if (entries.isEmpty()) {
-            throw RuntimeException("WindowManager Trace has no entries")
-        }
-        val lastWmState = entries.last()
-        return lastWmState.policy?.rotation
-            ?: run { throw RuntimeException("Wm state has no policy") }
-    }
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): WindowManagerTrace {
-        return WindowManagerTrace(
-            entries
-                .dropWhile { it.timestamp < startTimestamp }
-                .dropLastWhile { it.timestamp > endTimestamp }
-                .toTypedArray()
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTraceEntryBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTraceEntryBuilder.kt
deleted file mode 100644
index 43254e5..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/WindowManagerTraceEntryBuilder.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.traces.common.windowmanager
-
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
-import kotlin.js.JsName
-
-class WindowManagerTraceEntryBuilder(
-    _elapsedTimestamp: String,
-    @JsName("policy") private val policy: WindowManagerPolicy?,
-    @JsName("focusedApp") private val focusedApp: String,
-    @JsName("focusedDisplayId") private val focusedDisplayId: Int,
-    @JsName("focusedWindow") private val focusedWindow: String,
-    @JsName("inputMethodWindowAppToken") private val inputMethodWindowAppToken: String,
-    @JsName("isHomeRecentsComponent") private val isHomeRecentsComponent: Boolean,
-    @JsName("isDisplayFrozen") private val isDisplayFrozen: Boolean,
-    @JsName("pendingActivities") private val pendingActivities: Array<String>,
-    @JsName("root") private val root: RootWindowContainer,
-    @JsName("keyguardControllerState") private val keyguardControllerState: KeyguardControllerState,
-    @JsName("where") private val where: String = "",
-    realToElapsedTimeOffsetNs: String? = null,
-) {
-    // Necessary for compatibility with JS number type
-    @JsName("elapsedTimestamp") private val elapsedTimestamp: Long = _elapsedTimestamp.toLong()
-    @JsName("realTimestamp")
-    private val realTimestamp: Long? =
-        if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs.toLong() != 0L) {
-            realToElapsedTimeOffsetNs.toLong() + _elapsedTimestamp.toLong()
-        } else {
-            null
-        }
-
-    /** Constructs the window manager trace entry. */
-    @JsName("build")
-    fun build(): WindowManagerState {
-        return WindowManagerState(
-            elapsedTimestamp,
-            realTimestamp,
-            where,
-            policy,
-            focusedApp,
-            focusedDisplayId,
-            focusedWindow,
-            inputMethodWindowAppToken,
-            isHomeRecentsComponent,
-            isDisplayFrozen,
-            pendingActivities,
-            root,
-            keyguardControllerState
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt
deleted file mode 100644
index c40e22d..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Activity.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import kotlin.js.JsName
-
-/**
- * Represents an activity in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class Activity(
-    name: String,
-    @JsName("state") val state: String,
-    visible: Boolean,
-    @JsName("frontOfTask") val frontOfTask: Boolean,
-    @JsName("procId") val procId: Int,
-    @JsName("isTranslucent") val isTranslucent: Boolean,
-    windowContainer: WindowContainer
-) : WindowContainer(windowContainer, name, visible) {
-    /**
-     * Checks if the activity contains a [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getWindows")
-    fun getWindows(componentMatcher: IComponentMatcher): Array<WindowState> = getWindows {
-        componentMatcher.windowMatchesAnyOf(it)
-    }
-
-    /**
-     * Checks if the activity contains a [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("hasWindow")
-    fun hasWindow(componentMatcher: IComponentMatcher): Boolean =
-        getWindows(componentMatcher).isNotEmpty()
-
-    @JsName("hasWindowState")
-    internal fun hasWindowState(windowState: WindowState): Boolean =
-        getWindows { windowState == it }.isNotEmpty()
-
-    @JsName("isTablet")
-    private fun getWindows(predicate: (WindowState) -> Boolean) =
-        collectDescendants<WindowState> { predicate(it) }
-
-    override fun toString(): String {
-        return "${this::class.simpleName}: {$token $title} state=$state visible=$isVisible"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Activity) return false
-
-        if (state != other.state) return false
-        if (frontOfTask != other.frontOfTask) return false
-        if (procId != other.procId) return false
-        if (isTranslucent != other.isTranslucent) return false
-        if (orientation != other.orientation) return false
-        if (title != other.title) return false
-        if (token != other.token) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + state.hashCode()
-        result = 31 * result + frontOfTask.hashCode()
-        result = 31 * result + procId
-        result = 31 * result + isTranslucent.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt
deleted file mode 100644
index 5304c58..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Configuration.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Represents the configuration of a WM container
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class Configuration
-private constructor(
-    @JsName("windowConfiguration") val windowConfiguration: WindowConfiguration? = null,
-    @JsName("densityDpi") val densityDpi: Int = 0,
-    @JsName("orientation") val orientation: Int = 0,
-    @JsName("screenHeightDp") val screenHeightDp: Int = 0,
-    @JsName("screenWidthDp") val screenWidthDp: Int = 0,
-    @JsName("smallestScreenWidthDp") val smallestScreenWidthDp: Int = 0,
-    @JsName("screenLayout") val screenLayout: Int = 0,
-    @JsName("uiMode") val uiMode: Int = 0
-) {
-    @JsName("isEmpty")
-    val isEmpty: Boolean
-        get() =
-            (windowConfiguration == null) &&
-                densityDpi == 0 &&
-                orientation == 0 &&
-                screenHeightDp == 0 &&
-                screenWidthDp == 0 &&
-                smallestScreenWidthDp == 0 &&
-                screenLayout == 0 &&
-                uiMode == 0
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Configuration) return false
-
-        if (windowConfiguration != other.windowConfiguration) return false
-        if (densityDpi != other.densityDpi) return false
-        if (orientation != other.orientation) return false
-        if (screenHeightDp != other.screenHeightDp) return false
-        if (screenWidthDp != other.screenWidthDp) return false
-        if (smallestScreenWidthDp != other.smallestScreenWidthDp) return false
-        if (screenLayout != other.screenLayout) return false
-        if (uiMode != other.uiMode) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = windowConfiguration?.hashCode() ?: 0
-        result = 31 * result + densityDpi
-        result = 31 * result + orientation
-        result = 31 * result + screenHeightDp
-        result = 31 * result + screenWidthDp
-        result = 31 * result + smallestScreenWidthDp
-        result = 31 * result + screenLayout
-        result = 31 * result + uiMode
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: Configuration
-            get() = withCache { Configuration() }
-
-        @JsName("from")
-        fun from(
-            windowConfiguration: WindowConfiguration?,
-            densityDpi: Int,
-            orientation: Int,
-            screenHeightDp: Int,
-            screenWidthDp: Int,
-            smallestScreenWidthDp: Int,
-            screenLayout: Int,
-            uiMode: Int
-        ): Configuration = withCache {
-            Configuration(
-                windowConfiguration,
-                densityDpi,
-                orientation,
-                screenHeightDp,
-                screenWidthDp,
-                smallestScreenWidthDp,
-                screenLayout,
-                uiMode
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt
deleted file mode 100644
index c5e2e60..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/ConfigurationContainer.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import kotlin.js.JsName
-
-/**
- * Represents the configuration of an element in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-open class ConfigurationContainer(
-    @JsName("overrideConfiguration") val overrideConfiguration: Configuration?,
-    @JsName("fullConfiguration") val fullConfiguration: Configuration?,
-    @JsName("mergedOverrideConfiguration") val mergedOverrideConfiguration: Configuration?
-) {
-    constructor(
-        configurationContainer: ConfigurationContainer
-    ) : this(
-        configurationContainer.overrideConfiguration,
-        configurationContainer.fullConfiguration,
-        configurationContainer.mergedOverrideConfiguration
-    )
-
-    @JsName("windowingMode")
-    val windowingMode: Int
-        get() = fullConfiguration?.windowConfiguration?.windowingMode ?: 0
-
-    @JsName("activityType")
-    open val activityType: Int
-        get() = fullConfiguration?.windowConfiguration?.activityType ?: 0
-
-    @JsName("isEmpty")
-    open val isEmpty: Boolean
-        get() =
-            (overrideConfiguration?.isEmpty
-                ?: true) &&
-                (fullConfiguration?.isEmpty ?: true) &&
-                (mergedOverrideConfiguration?.isEmpty ?: true)
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ConfigurationContainer) return false
-
-        if (overrideConfiguration != other.overrideConfiguration) return false
-        if (fullConfiguration != other.fullConfiguration) return false
-        if (mergedOverrideConfiguration != other.mergedOverrideConfiguration) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = overrideConfiguration?.hashCode() ?: 0
-        result = 31 * result + (fullConfiguration?.hashCode() ?: 0)
-        result = 31 * result + (mergedOverrideConfiguration?.hashCode() ?: 0)
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt
deleted file mode 100644
index 1e485ef..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayArea.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import kotlin.js.JsName
-
-/**
- * Represents a display area in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class DisplayArea(
-    @JsName("isTaskDisplayArea") val isTaskDisplayArea: Boolean,
-    windowContainer: WindowContainer
-) : WindowContainer(windowContainer) {
-    @JsName("activities")
-    val activities: Array<Activity>
-        get() =
-            if (isTaskDisplayArea) {
-                this.collectDescendants()
-            } else {
-                emptyArray()
-            }
-
-    /**
-     * @return if [componentMatcher] matches any activity
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("containsActivity")
-    fun containsActivity(componentMatcher: IComponentMatcher): Boolean {
-        return if (!isTaskDisplayArea) {
-            false
-        } else {
-            componentMatcher.activityMatchesAnyOf(activities)
-        }
-    }
-
-    override fun toString(): String {
-        return "${this::class.simpleName} {$token $title} isTaskArea=$isTaskDisplayArea"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is DisplayArea) return false
-
-        if (isTaskDisplayArea != other.isTaskDisplayArea) return false
-        if (isVisible != other.isVisible) return false
-        if (orientation != other.orientation) return false
-        if (title != other.title) return false
-        if (token != other.token) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + isTaskDisplayArea.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt
deleted file mode 100644
index fad9104..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayContent.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.service.PlatformConsts
-import kotlin.js.JsName
-import kotlin.math.min
-
-/**
- * Represents a display content in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class DisplayContent(
-    @JsName("id") val id: Int,
-    @JsName("focusedRootTaskId") val focusedRootTaskId: Int,
-    @JsName("resumedActivity") val resumedActivity: String,
-    @JsName("singleTaskInstance") val singleTaskInstance: Boolean,
-    @JsName("defaultPinnedStackBounds") val defaultPinnedStackBounds: Rect,
-    @JsName("pinnedStackMovementBounds") val pinnedStackMovementBounds: Rect,
-    @JsName("displayRect") val displayRect: Rect,
-    @JsName("appRect") val appRect: Rect,
-    @JsName("dpi") val dpi: Int,
-    @JsName("flags") val flags: Int,
-    @JsName("stableBounds") val stableBounds: Rect,
-    @JsName("surfaceSize") val surfaceSize: Int,
-    @JsName("focusedApp") val focusedApp: String,
-    @JsName("lastTransition") val lastTransition: String,
-    @JsName("appTransitionState") val appTransitionState: String,
-    @JsName("rotation") val rotation: PlatformConsts.Rotation,
-    @JsName("lastOrientation") val lastOrientation: Int,
-    @JsName("cutout") val cutout: DisplayCutout?,
-    windowContainer: WindowContainer
-) : WindowContainer(windowContainer) {
-    override val name: String = id.toString()
-    override val isVisible: Boolean = false
-
-    @JsName("isTablet")
-    val isTablet: Boolean
-        get() {
-            val smallestWidth =
-                dpiFromPx(min(displayRect.width.toFloat(), displayRect.height.toFloat()), dpi)
-            return smallestWidth >= TABLET_MIN_DPS
-        }
-
-    @JsName("rootTasks")
-    val rootTasks: Array<Task>
-        get() {
-            val tasks = this.collectDescendants<Task> { it.isRootTask }.toMutableList()
-            // TODO(b/149338177): figure out how CTS tests deal with organizer. For now,
-            //                    don't treat them as regular stacks
-            val rootOrganizedTasks = mutableListOf<Task>()
-            val reversedTaskList = tasks.reversed()
-            reversedTaskList.forEach { task ->
-                // Skip tasks created by an organizer
-                if (task.createdByOrganizer) {
-                    tasks.remove(task)
-                    rootOrganizedTasks.add(task)
-                }
-            }
-            // Add root tasks controlled by an organizer
-            rootOrganizedTasks.reversed().forEach { task ->
-                tasks.addAll(task.children.reversed().map { it as Task })
-            }
-
-            return tasks.toTypedArray()
-        }
-
-    /**
-     * @return if [componentMatcher] matches any activity
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("containsActivity")
-    fun containsActivity(componentMatcher: IComponentMatcher): Boolean =
-        rootTasks.any { it.containsActivity(componentMatcher) }
-
-    /**
-     * @return THe [DisplayArea] matching [componentMatcher], or null if none matches
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getTaskDisplayArea")
-    fun getTaskDisplayArea(componentMatcher: IComponentMatcher): DisplayArea? {
-        val taskDisplayAreas =
-            this.collectDescendants<DisplayArea> { it.isTaskDisplayArea }
-                .filter { it.containsActivity(componentMatcher) }
-
-        if (taskDisplayAreas.size > 1) {
-            throw IllegalArgumentException(
-                "There must be exactly one activity among all TaskDisplayAreas."
-            )
-        }
-
-        return taskDisplayAreas.firstOrNull()
-    }
-
-    override fun toString(): String {
-        return "${this::class.simpleName} #$id: name=$title mDisplayRect=$displayRect " +
-            "mAppRect=$appRect mFlags=$flags"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is DisplayContent) return false
-        if (!super.equals(other)) return false
-
-        if (id != other.id) return false
-        if (focusedRootTaskId != other.focusedRootTaskId) return false
-        if (resumedActivity != other.resumedActivity) return false
-        if (defaultPinnedStackBounds != other.defaultPinnedStackBounds) return false
-        if (pinnedStackMovementBounds != other.pinnedStackMovementBounds) return false
-        if (stableBounds != other.stableBounds) return false
-        if (displayRect != other.displayRect) return false
-        if (appRect != other.appRect) return false
-        if (dpi != other.dpi) return false
-        if (flags != other.flags) return false
-        if (focusedApp != other.focusedApp) return false
-        if (lastTransition != other.lastTransition) return false
-        if (appTransitionState != other.appTransitionState) return false
-        if (rotation != other.rotation) return false
-        if (lastOrientation != other.lastOrientation) return false
-        if (cutout != other.cutout) return false
-        if (name != other.name) return false
-        if (singleTaskInstance != other.singleTaskInstance) return false
-        if (surfaceSize != other.surfaceSize) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + id
-        result = 31 * result + focusedRootTaskId
-        result = 31 * result + resumedActivity.hashCode()
-        result = 31 * result + singleTaskInstance.hashCode()
-        result = 31 * result + defaultPinnedStackBounds.hashCode()
-        result = 31 * result + pinnedStackMovementBounds.hashCode()
-        result = 31 * result + displayRect.hashCode()
-        result = 31 * result + appRect.hashCode()
-        result = 31 * result + dpi
-        result = 31 * result + flags
-        result = 31 * result + stableBounds.hashCode()
-        result = 31 * result + surfaceSize
-        result = 31 * result + focusedApp.hashCode()
-        result = 31 * result + lastTransition.hashCode()
-        result = 31 * result + appTransitionState.hashCode()
-        result = 31 * result + rotation.value
-        result = 31 * result + lastOrientation
-        result = 31 * result + cutout.hashCode()
-        result = 31 * result + name.hashCode()
-        result = 31 * result + isVisible.hashCode()
-        return result
-    }
-
-    companion object {
-        /** From [android.util.DisplayMetrics] */
-        @JsName("DENSITY_DEFAULT") private const val DENSITY_DEFAULT = 160f
-        /** From [com.android.systemui.shared.recents.utilities.Utilities] */
-        @JsName("TABLET_MIN_DPS") private const val TABLET_MIN_DPS = 600f
-
-        @JsName("dpiFromPx")
-        private fun dpiFromPx(size: Float, densityDpi: Int): Float {
-            val densityRatio: Float = densityDpi.toFloat() / DENSITY_DEFAULT
-            return size / densityRatio
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayCutout.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayCutout.kt
deleted file mode 100644
index 2a98abd..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/DisplayCutout.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Insets
-import com.android.server.wm.traces.common.Rect
-
-/** Representation of a display cutout from a WM trace */
-data class DisplayCutout(
-    val insets: Insets,
-    val boundLeft: Rect,
-    val boundTop: Rect,
-    val boundRight: Rect,
-    val boundBottom: Rect,
-    val waterfallInsets: Insets
-)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/KeyguardControllerState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/KeyguardControllerState.kt
deleted file mode 100644
index 149d2f8..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/KeyguardControllerState.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Represents the keyguard controller in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class KeyguardControllerState
-private constructor(
-    @JsName("isAodShowing") val isAodShowing: Boolean,
-    @JsName("isKeyguardShowing") val isKeyguardShowing: Boolean,
-    @JsName("keyguardOccludedStates") val keyguardOccludedStates: Map<Int, Boolean>
-) {
-    @JsName("isKeyguardOccluded")
-    fun isKeyguardOccluded(displayId: Int): Boolean = keyguardOccludedStates[displayId] ?: false
-
-    override fun toString(): String {
-        return "KeyguardControllerState: {aod=$isAodShowing keyguard=$isKeyguardShowing}"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is KeyguardControllerState) return false
-
-        if (isAodShowing != other.isAodShowing) return false
-        if (isKeyguardShowing != other.isKeyguardShowing) return false
-        if (keyguardOccludedStates != other.keyguardOccludedStates) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = isAodShowing.hashCode()
-        result = 31 * result + isKeyguardShowing.hashCode()
-        result = 31 * result + keyguardOccludedStates.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("from")
-        fun from(
-            isAodShowing: Boolean,
-            isKeyguardShowing: Boolean,
-            keyguardOccludedStates: Map<Int, Boolean>
-        ): KeyguardControllerState = withCache {
-            KeyguardControllerState(isAodShowing, isKeyguardShowing, keyguardOccludedStates)
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/RootWindowContainer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/RootWindowContainer.kt
deleted file mode 100644
index cf3f8ac..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/RootWindowContainer.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-/**
- * Represents the root window container in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class RootWindowContainer(windowContainer: WindowContainer) : WindowContainer(windowContainer) {
-    override fun toString(): String {
-        return "${this::class.simpleName}: {$token $title}"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt
deleted file mode 100644
index ed0249b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/Task.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import kotlin.js.JsName
-
-/**
- * Represents a task in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class Task(
-    override val activityType: Int,
-    override val isFullscreen: Boolean,
-    override val bounds: Rect,
-    @JsName("taskId") val taskId: Int,
-    @JsName("rootTaskId") val rootTaskId: Int,
-    @JsName("displayId") val displayId: Int,
-    @JsName("lastNonFullscreenBounds") val lastNonFullscreenBounds: Rect,
-    @JsName("realActivity") val realActivity: String,
-    @JsName("origActivity") val origActivity: String,
-    @JsName("resizeMode") val resizeMode: Int,
-    @JsName("_resumedActivity") private val _resumedActivity: String,
-    @JsName("animatingBounds") var animatingBounds: Boolean,
-    @JsName("surfaceWidth") val surfaceWidth: Int,
-    @JsName("surfaceHeight") val surfaceHeight: Int,
-    @JsName("createdByOrganizer") val createdByOrganizer: Boolean,
-    @JsName("minWidth") val minWidth: Int,
-    @JsName("minHeight") val minHeight: Int,
-    windowContainer: WindowContainer
-) : WindowContainer(windowContainer) {
-    override val isVisible: Boolean = false
-    override val name: String = taskId.toString()
-    override val isEmpty: Boolean
-        get() = tasks.isEmpty() && activities.isEmpty()
-    override val stableId: String
-        get() = "${super.stableId} $taskId"
-
-    @JsName("isRootTask")
-    val isRootTask: Boolean
-        get() = taskId == rootTaskId
-    @JsName("tasks")
-    val tasks: Array<Task>
-        get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
-    @JsName("taskFragments")
-    val taskFragments: Array<TaskFragment>
-        get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
-    @JsName("activities")
-    val activities: Array<Activity>
-        get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
-    /** The top task in the stack. */
-    // NOTE: Unlike the WindowManager internals, we dump the state from top to bottom,
-    //       so the indices are inverted
-    @JsName("topTask")
-    val topTask: Task?
-        get() = tasks.firstOrNull()
-    @JsName("resumedActivities")
-    val resumedActivities: Array<String>
-        get() {
-            val result = mutableSetOf<String>()
-            if (this._resumedActivity.isNotEmpty()) {
-                result.add(this._resumedActivity)
-            }
-            val activitiesInChildren =
-                this.tasks.flatMap { it.resumedActivities.toList() }.filter { it.isNotEmpty() }
-            result.addAll(activitiesInChildren)
-            return result.toTypedArray()
-        }
-
-    /** @return The first [Task] matching [predicate], or null otherwise */
-    @JsName("getTask")
-    fun getTask(predicate: (Task) -> Boolean) =
-        tasks.firstOrNull { predicate(it) } ?: if (predicate(this)) this else null
-
-    /** @return the first [Activity] matching [predicate], or null otherwise */
-    @JsName("getActivityByPredicate")
-    internal fun getActivity(predicate: (Activity) -> Boolean): Activity? {
-        var activity: Activity? = activities.firstOrNull { predicate(it) }
-        if (activity != null) {
-            return activity
-        }
-        for (task in tasks) {
-            activity = task.getActivity(predicate)
-            if (activity != null) {
-                return activity
-            }
-        }
-        for (taskFragment in taskFragments) {
-            activity = taskFragment.getActivity(predicate)
-            if (activity != null) {
-                return activity
-            }
-        }
-        return null
-    }
-
-    /**
-     * @return the first [Activity] matching [componentMatcher], or null otherwise
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("getActivity")
-    fun getActivity(componentMatcher: IComponentMatcher): Activity? = getActivity { activity ->
-        componentMatcher.activityMatchesAnyOf(activity)
-    }
-
-    /**
-     * @return if any activity matches [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    @JsName("containsActivity")
-    fun containsActivity(componentMatcher: IComponentMatcher) =
-        getActivity(componentMatcher) != null
-
-    override fun toString(): String {
-        return "${this::class.simpleName}: {$token $title} id=$taskId bounds=$bounds"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is Task) return false
-
-        if (activityType != other.activityType) return false
-        if (isFullscreen != other.isFullscreen) return false
-        if (bounds != other.bounds) return false
-        if (taskId != other.taskId) return false
-        if (rootTaskId != other.rootTaskId) return false
-        if (displayId != other.displayId) return false
-        if (realActivity != other.realActivity) return false
-        if (resizeMode != other.resizeMode) return false
-        if (minWidth != other.minWidth) return false
-        if (minHeight != other.minHeight) return false
-        if (name != other.name) return false
-        if (orientation != other.orientation) return false
-        if (title != other.title) return false
-        if (token != other.token) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + activityType
-        result = 31 * result + isFullscreen.hashCode()
-        result = 31 * result + bounds.hashCode()
-        result = 31 * result + taskId
-        result = 31 * result + rootTaskId
-        result = 31 * result + displayId
-        result = 31 * result + lastNonFullscreenBounds.hashCode()
-        result = 31 * result + realActivity.hashCode()
-        result = 31 * result + origActivity.hashCode()
-        result = 31 * result + resizeMode
-        result = 31 * result + _resumedActivity.hashCode()
-        result = 31 * result + animatingBounds.hashCode()
-        result = 31 * result + surfaceWidth
-        result = 31 * result + surfaceHeight
-        result = 31 * result + createdByOrganizer.hashCode()
-        result = 31 * result + minWidth
-        result = 31 * result + minHeight
-        result = 31 * result + isVisible.hashCode()
-        result = 31 * result + name.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt
deleted file mode 100644
index 4f3d16c..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/TaskFragment.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.windowmanager.windows
-
-import kotlin.js.JsName
-
-/**
- * Represents a task fragment in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class TaskFragment(
-    override val activityType: Int,
-    @JsName("displayId") val displayId: Int,
-    @JsName("minWidth") val minWidth: Int,
-    @JsName("minHeight") val minHeight: Int,
-    windowContainer: WindowContainer
-) : WindowContainer(windowContainer) {
-    @JsName("tasks")
-    val tasks: Array<Task>
-        get() = this.children.reversed().filterIsInstance<Task>().toTypedArray()
-    @JsName("taskFragments")
-    val taskFragments: Array<TaskFragment>
-        get() = this.children.reversed().filterIsInstance<TaskFragment>().toTypedArray()
-    @JsName("activities")
-    val activities: Array<Activity>
-        get() = this.children.reversed().filterIsInstance<Activity>().toTypedArray()
-
-    @JsName("getActivity")
-    fun getActivity(predicate: (Activity) -> Boolean): Activity? {
-        var activity: Activity? = activities.firstOrNull { predicate(it) }
-        if (activity != null) {
-            return activity
-        }
-        for (task in tasks) {
-            activity = task.getActivity(predicate)
-            if (activity != null) {
-                return activity
-            }
-        }
-        for (taskFragment in taskFragments) {
-            activity = taskFragment.getActivity(predicate)
-            if (activity != null) {
-                return activity
-            }
-        }
-        return null
-    }
-
-    override fun toString(): String {
-        return "${this::class.simpleName}: {$token $title} bounds=$bounds"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is TaskFragment) return false
-
-        if (activityType != other.activityType) return false
-        if (displayId != other.displayId) return false
-        if (minWidth != other.minWidth) return false
-        if (minHeight != other.minHeight) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + activityType
-        result = 31 * result + displayId
-        result = 31 * result + minWidth
-        result = 31 * result + minHeight
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt
deleted file mode 100644
index f95da9f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowConfiguration.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Represents the configuration of a WM window
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-open class WindowConfiguration(
-    @JsName("appBounds") val appBounds: Rect = Rect.EMPTY,
-    @JsName("bounds") val bounds: Rect = Rect.EMPTY,
-    @JsName("maxBounds") val maxBounds: Rect = Rect.EMPTY,
-    @JsName("windowingMode") val windowingMode: Int = 0,
-    @JsName("activityType") val activityType: Int = 0
-) {
-    @JsName("isEmpty")
-    val isEmpty: Boolean
-        get() =
-            appBounds.isEmpty &&
-                bounds.isEmpty &&
-                maxBounds.isEmpty &&
-                windowingMode == 0 &&
-                activityType == 0
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowConfiguration) return false
-
-        if (windowingMode != other.windowingMode) return false
-        if (activityType != other.activityType) return false
-        if (appBounds != other.appBounds) return false
-        if (bounds != other.bounds) return false
-        if (maxBounds != other.maxBounds) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = windowingMode
-        result = 31 * result + activityType
-        result = 31 * result + appBounds.hashCode()
-        result = 31 * result + bounds.hashCode()
-        result = 31 * result + maxBounds.hashCode()
-        return result
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: WindowConfiguration
-            get() = withCache { WindowConfiguration() }
-        @JsName("from")
-        fun from(
-            appBounds: Rect?,
-            bounds: Rect?,
-            maxBounds: Rect?,
-            windowingMode: Int,
-            activityType: Int
-        ): WindowConfiguration = withCache {
-            WindowConfiguration(
-                appBounds ?: Rect.EMPTY,
-                bounds ?: Rect.EMPTY,
-                maxBounds ?: Rect.EMPTY,
-                windowingMode,
-                activityType
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt
deleted file mode 100644
index 9737368..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowContainer.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Rect
-import kotlin.js.JsName
-
-/**
- * Represents WindowContainer classes such as DisplayContent.WindowContainers and
- * DisplayContent.NonAppWindowContainers. This can be expanded into a specific class if we need
- * track and assert some state in the future.
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-open class WindowContainer
-constructor(
-    @JsName("title") val title: String,
-    @JsName("token") val token: String,
-    @JsName("orientation") val orientation: Int,
-    @JsName("layerId") val layerId: Int,
-    _isVisible: Boolean,
-    configurationContainer: ConfigurationContainer,
-    @JsName("children") val children: Array<WindowContainer>,
-    @JsName("computedZ") val computedZ: Int
-) : ConfigurationContainer(configurationContainer) {
-    protected constructor(
-        windowContainer: WindowContainer,
-        titleOverride: String? = null,
-        isVisibleOverride: Boolean? = null
-    ) : this(
-        titleOverride ?: windowContainer.title,
-        windowContainer.token,
-        windowContainer.orientation,
-        windowContainer.layerId,
-        isVisibleOverride ?: windowContainer.isVisible,
-        windowContainer,
-        windowContainer.children,
-        windowContainer.computedZ
-    )
-
-    @JsName("parent")
-    var parent: WindowContainer? = null
-        private set
-
-    init {
-        children.forEach { it.parent = this }
-    }
-
-    @JsName("isVisible") open val isVisible: Boolean = _isVisible
-    @JsName("name") open val name: String = title
-    @JsName("stableId")
-    open val stableId: String
-        get() = "${this::class.simpleName} $token $title"
-    @JsName("isFullscreen") open val isFullscreen: Boolean = false
-    @JsName("bounds") open val bounds: Rect = Rect.EMPTY
-
-    @JsName("traverseTopDown")
-    fun traverseTopDown(): List<WindowContainer> {
-        val traverseList = mutableListOf(this)
-
-        this.children.reversed().forEach { childLayer ->
-            traverseList.addAll(childLayer.traverseTopDown())
-        }
-
-        return traverseList
-    }
-
-    /**
-     * For a given WindowContainer, traverse down the hierarchy and collect all children of type [T]
-     * if the child passes the test [predicate].
-     *
-     * @param predicate Filter function
-     */
-    inline fun <reified T : WindowContainer> collectDescendants(
-        predicate: (T) -> Boolean = { true }
-    ): Array<T> {
-        val traverseList = traverseTopDown()
-
-        return traverseList.filterIsInstance<T>().filter { predicate(it) }.toTypedArray()
-    }
-
-    override fun toString(): String {
-        if (
-            this.title.isEmpty() ||
-                listOf("WindowContainer", "Task").any { it.contains(this.title) }
-        ) {
-            return ""
-        }
-
-        return "$${removeRedundancyInName(this.title)}@${this.token}"
-    }
-
-    private fun removeRedundancyInName(name: String): String {
-        if (!name.contains('/')) {
-            return name
-        }
-
-        val split = name.split('/')
-        val pkg = split[0]
-        var clazz = split.slice(1..split.lastIndex).joinToString("/")
-
-        if (clazz.startsWith("$pkg.")) {
-            clazz = clazz.slice(pkg.length + 1..clazz.lastIndex)
-
-            return "$pkg/$clazz"
-        }
-
-        return name
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowContainer) return false
-
-        if (title != other.title) return false
-        if (token != other.token) return false
-        if (orientation != other.orientation) return false
-        if (isVisible != other.isVisible) return false
-        if (name != other.name) return false
-        if (isFullscreen != other.isFullscreen) return false
-        if (bounds != other.bounds) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = title.hashCode()
-        result = 31 * result + token.hashCode()
-        result = 31 * result + orientation
-        result = 31 * result + children.contentHashCode()
-        result = 31 * result + isVisible.hashCode()
-        result = 31 * result + name.hashCode()
-        result = 31 * result + isFullscreen.hashCode()
-        result = 31 * result + bounds.hashCode()
-        return result
-    }
-
-    override val isEmpty: Boolean
-        get() = super.isEmpty && title.isEmpty() && token.isEmpty()
-
-    companion object {
-        fun withTitle(title: String): WindowContainer {
-            val emptyConfigurationContainer = ConfigurationContainer(null, null, null)
-            return WindowContainer(
-                title,
-                "",
-                0,
-                0,
-                false,
-                emptyConfigurationContainer,
-                arrayOf(),
-                computedZ = -1
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt
deleted file mode 100644
index b009a09..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowLayoutParams.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Represents the attributes of a WindowState in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class WindowLayoutParams
-private constructor(
-    @JsName("type") val type: Int = 0,
-    @JsName("x") val x: Int = 0,
-    @JsName("y") val y: Int = 0,
-    @JsName("width") val width: Int = 0,
-    @JsName("height") val height: Int = 0,
-    @JsName("horizontalMargin") val horizontalMargin: Float = 0f,
-    @JsName("verticalMargin") val verticalMargin: Float = 0f,
-    @JsName("gravity") val gravity: Int = 0,
-    @JsName("softInputMode") val softInputMode: Int = 0,
-    @JsName("format") val format: Int = 0,
-    @JsName("windowAnimations") val windowAnimations: Int = 0,
-    @JsName("alpha") val alpha: Float = 0f,
-    @JsName("screenBrightness") val screenBrightness: Float = 0f,
-    @JsName("buttonBrightness") val buttonBrightness: Float = 0f,
-    @JsName("rotationAnimation") val rotationAnimation: Int = 0,
-    @JsName("preferredRefreshRate") val preferredRefreshRate: Float = 0f,
-    @JsName("preferredDisplayModeId") val preferredDisplayModeId: Int = 0,
-    @JsName("hasSystemUiListeners") val hasSystemUiListeners: Boolean = false,
-    @JsName("inputFeatureFlags") val inputFeatureFlags: Int = 0,
-    @JsName("userActivityTimeout") val userActivityTimeout: Long = 0L,
-    @JsName("colorMode") val colorMode: Int = 0,
-    @JsName("flags") val flags: Int = 0,
-    @JsName("privateFlags") val privateFlags: Int = 0,
-    @JsName("systemUiVisibilityFlags") val systemUiVisibilityFlags: Int = 0,
-    @JsName("subtreeSystemUiVisibilityFlags") val subtreeSystemUiVisibilityFlags: Int = 0,
-    @JsName("appearance") val appearance: Int = 0,
-    @JsName("behavior") val behavior: Int = 0,
-    @JsName("fitInsetsTypes") val fitInsetsTypes: Int = 0,
-    @JsName("fitInsetsSides") val fitInsetsSides: Int = 0,
-    @JsName("fitIgnoreVisibility") val fitIgnoreVisibility: Boolean = false
-) {
-    @JsName("isValidNavBarType") val isValidNavBarType: Boolean = this.type == TYPE_NAVIGATION_BAR
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowLayoutParams) return false
-
-        if (type != other.type) return false
-        if (x != other.x) return false
-        if (y != other.y) return false
-        if (width != other.width) return false
-        if (height != other.height) return false
-        if (horizontalMargin != other.horizontalMargin) return false
-        if (verticalMargin != other.verticalMargin) return false
-        if (gravity != other.gravity) return false
-        if (softInputMode != other.softInputMode) return false
-        if (format != other.format) return false
-        if (windowAnimations != other.windowAnimations) return false
-        if (alpha != other.alpha) return false
-        if (screenBrightness != other.screenBrightness) return false
-        if (buttonBrightness != other.buttonBrightness) return false
-        if (rotationAnimation != other.rotationAnimation) return false
-        if (preferredRefreshRate != other.preferredRefreshRate) return false
-        if (preferredDisplayModeId != other.preferredDisplayModeId) return false
-        if (hasSystemUiListeners != other.hasSystemUiListeners) return false
-        if (inputFeatureFlags != other.inputFeatureFlags) return false
-        if (userActivityTimeout != other.userActivityTimeout) return false
-        if (colorMode != other.colorMode) return false
-        if (flags != other.flags) return false
-        if (privateFlags != other.privateFlags) return false
-        if (systemUiVisibilityFlags != other.systemUiVisibilityFlags) return false
-        if (subtreeSystemUiVisibilityFlags != other.subtreeSystemUiVisibilityFlags) return false
-        if (appearance != other.appearance) return false
-        if (behavior != other.behavior) return false
-        if (fitInsetsTypes != other.fitInsetsTypes) return false
-        if (fitInsetsSides != other.fitInsetsSides) return false
-        if (fitIgnoreVisibility != other.fitIgnoreVisibility) return false
-        if (isValidNavBarType != other.isValidNavBarType) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = type
-        result = 31 * result + x
-        result = 31 * result + y
-        result = 31 * result + width
-        result = 31 * result + height
-        result = 31 * result + horizontalMargin.hashCode()
-        result = 31 * result + verticalMargin.hashCode()
-        result = 31 * result + gravity
-        result = 31 * result + softInputMode
-        result = 31 * result + format
-        result = 31 * result + windowAnimations
-        result = 31 * result + alpha.hashCode()
-        result = 31 * result + screenBrightness.hashCode()
-        result = 31 * result + buttonBrightness.hashCode()
-        result = 31 * result + rotationAnimation
-        result = 31 * result + preferredRefreshRate.hashCode()
-        result = 31 * result + preferredDisplayModeId
-        result = 31 * result + hasSystemUiListeners.hashCode()
-        result = 31 * result + inputFeatureFlags
-        result = 31 * result + userActivityTimeout.hashCode()
-        result = 31 * result + colorMode
-        result = 31 * result + flags
-        result = 31 * result + privateFlags
-        result = 31 * result + systemUiVisibilityFlags
-        result = 31 * result + subtreeSystemUiVisibilityFlags
-        result = 31 * result + appearance
-        result = 31 * result + behavior
-        result = 31 * result + fitInsetsTypes
-        result = 31 * result + fitInsetsSides
-        result = 31 * result + fitIgnoreVisibility.hashCode()
-        result = 31 * result + isValidNavBarType.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "WindowLayoutParams(type=$type, x=$x, y=$y, width=$width, height=$height, " +
-            "horizontalMargin=$horizontalMargin, verticalMargin=$verticalMargin, " +
-            "gravity=$gravity, softInputMode=$softInputMode, format=$format, " +
-            "windowAnimations=$windowAnimations, alpha=$alpha, " +
-            "screenBrightness=$screenBrightness, buttonBrightness=$buttonBrightness, " +
-            "rotationAnimation=$rotationAnimation, preferredRefreshRate=$preferredRefreshRate, " +
-            "preferredDisplayModeId=$preferredDisplayModeId, " +
-            "hasSystemUiListeners=$hasSystemUiListeners, inputFeatureFlags=$inputFeatureFlags, " +
-            "userActivityTimeout=$userActivityTimeout, colorMode=$colorMode, flags=$flags, " +
-            "privateFlags=$privateFlags, systemUiVisibilityFlags=$systemUiVisibilityFlags, " +
-            "subtreeSystemUiVisibilityFlags=$subtreeSystemUiVisibilityFlags, " +
-            "appearance=$appearance, behavior=$behavior, fitInsetsTypes=$fitInsetsTypes, " +
-            "fitInsetsSides=$fitInsetsSides, fitIgnoreVisibility=$fitIgnoreVisibility, " +
-            "isValidNavBarType=$isValidNavBarType)"
-    }
-
-    companion object {
-        val EMPTY: WindowLayoutParams
-            get() = withCache { WindowLayoutParams() }
-        /** @see WindowManager.LayoutParams */
-        private const val TYPE_NAVIGATION_BAR = 2019
-
-        @JsName("from")
-        fun from(
-            type: Int,
-            x: Int,
-            y: Int,
-            width: Int,
-            height: Int,
-            horizontalMargin: Float,
-            verticalMargin: Float,
-            gravity: Int,
-            softInputMode: Int,
-            format: Int,
-            windowAnimations: Int,
-            alpha: Float,
-            screenBrightness: Float,
-            buttonBrightness: Float,
-            rotationAnimation: Int,
-            preferredRefreshRate: Float,
-            preferredDisplayModeId: Int,
-            hasSystemUiListeners: Boolean,
-            inputFeatureFlags: Int,
-            userActivityTimeout: Long,
-            colorMode: Int,
-            flags: Int,
-            privateFlags: Int,
-            systemUiVisibilityFlags: Int,
-            subtreeSystemUiVisibilityFlags: Int,
-            appearance: Int,
-            behavior: Int,
-            fitInsetsTypes: Int,
-            fitInsetsSides: Int,
-            fitIgnoreVisibility: Boolean
-        ): WindowLayoutParams = withCache {
-            WindowLayoutParams(
-                type,
-                x,
-                y,
-                width,
-                height,
-                horizontalMargin,
-                verticalMargin,
-                gravity,
-                softInputMode,
-                format,
-                windowAnimations,
-                alpha,
-                screenBrightness,
-                buttonBrightness,
-                rotationAnimation,
-                preferredRefreshRate,
-                preferredDisplayModeId,
-                hasSystemUiListeners,
-                inputFeatureFlags,
-                userActivityTimeout,
-                colorMode,
-                flags,
-                privateFlags,
-                systemUiVisibilityFlags,
-                subtreeSystemUiVisibilityFlags,
-                appearance,
-                behavior,
-                fitInsetsTypes,
-                fitInsetsSides,
-                fitIgnoreVisibility
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt
deleted file mode 100644
index c92424f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowManagerPolicy.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.withCache
-import kotlin.js.JsName
-
-/**
- * Represents the requested policy of a WM container
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class WindowManagerPolicy
-private constructor(
-    @JsName("focusedAppToken") val focusedAppToken: String = "",
-    @JsName("forceStatusBar") val forceStatusBar: Boolean = false,
-    @JsName("forceStatusBarFromKeyguard") val forceStatusBarFromKeyguard: Boolean = false,
-    @JsName("keyguardDrawComplete") val keyguardDrawComplete: Boolean = false,
-    @JsName("keyguardOccluded") val keyguardOccluded: Boolean = false,
-    @JsName("keyguardOccludedChanged") val keyguardOccludedChanged: Boolean = false,
-    @JsName("keyguardOccludedPending") val keyguardOccludedPending: Boolean = false,
-    @JsName("lastSystemUiFlags") val lastSystemUiFlags: Int = 0,
-    @JsName("orientation") val orientation: Int = 0,
-    @JsName("rotation") val rotation: PlatformConsts.Rotation = PlatformConsts.Rotation.ROTATION_0,
-    @JsName("rotationMode") val rotationMode: Int = 0,
-    @JsName("screenOnFully") val screenOnFully: Boolean = false,
-    @JsName("windowManagerDrawComplete") val windowManagerDrawComplete: Boolean = false
-) {
-    @JsName("isOrientationNoSensor")
-    val isOrientationNoSensor: Boolean
-        get() = orientation == SCREEN_ORIENTATION_NOSENSOR
-
-    @JsName("isFixedOrientation")
-    val isFixedOrientation: Boolean
-        get() =
-            isFixedOrientationLandscape ||
-                isFixedOrientationPortrait ||
-                orientation == SCREEN_ORIENTATION_LOCKED
-
-    @JsName("isFixedOrientationLandscape")
-    private val isFixedOrientationLandscape
-        get() =
-            orientation == SCREEN_ORIENTATION_LANDSCAPE ||
-                orientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE ||
-                orientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE ||
-                orientation == SCREEN_ORIENTATION_USER_LANDSCAPE
-
-    @JsName("isFixedOrientationPortrait")
-    private val isFixedOrientationPortrait
-        get() =
-            orientation == SCREEN_ORIENTATION_PORTRAIT ||
-                orientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT ||
-                orientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
-                orientation == SCREEN_ORIENTATION_USER_PORTRAIT
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowManagerPolicy) return false
-
-        if (focusedAppToken != other.focusedAppToken) return false
-        if (forceStatusBar != other.forceStatusBar) return false
-        if (forceStatusBarFromKeyguard != other.forceStatusBarFromKeyguard) return false
-        if (keyguardDrawComplete != other.keyguardDrawComplete) return false
-        if (keyguardOccluded != other.keyguardOccluded) return false
-        if (keyguardOccludedChanged != other.keyguardOccludedChanged) return false
-        if (keyguardOccludedPending != other.keyguardOccludedPending) return false
-        if (lastSystemUiFlags != other.lastSystemUiFlags) return false
-        if (orientation != other.orientation) return false
-        if (rotation != other.rotation) return false
-        if (rotationMode != other.rotationMode) return false
-        if (screenOnFully != other.screenOnFully) return false
-        if (windowManagerDrawComplete != other.windowManagerDrawComplete) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = focusedAppToken.hashCode()
-        result = 31 * result + forceStatusBar.hashCode()
-        result = 31 * result + forceStatusBarFromKeyguard.hashCode()
-        result = 31 * result + keyguardDrawComplete.hashCode()
-        result = 31 * result + keyguardOccluded.hashCode()
-        result = 31 * result + keyguardOccludedChanged.hashCode()
-        result = 31 * result + keyguardOccludedPending.hashCode()
-        result = 31 * result + lastSystemUiFlags
-        result = 31 * result + orientation
-        result = 31 * result + rotation.hashCode()
-        result = 31 * result + rotationMode
-        result = 31 * result + screenOnFully.hashCode()
-        result = 31 * result + windowManagerDrawComplete.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "${this::class.simpleName} (focusedAppToken='$focusedAppToken', " +
-            "forceStatusBar=$forceStatusBar, " +
-            "forceStatusBarFromKeyguard=$forceStatusBarFromKeyguard, " +
-            "keyguardDrawComplete=$keyguardDrawComplete, keyguardOccluded=$keyguardOccluded, " +
-            "keyguardOccludedChanged=$keyguardOccludedChanged, " +
-            "keyguardOccludedPending=$keyguardOccludedPending, " +
-            "lastSystemUiFlags=$lastSystemUiFlags, orientation=$orientation, " +
-            "rotation=$rotation, rotationMode=$rotationMode, " +
-            "screenOnFully=$screenOnFully, " +
-            "windowManagerDrawComplete=$windowManagerDrawComplete)"
-    }
-
-    companion object {
-        @JsName("EMPTY")
-        val EMPTY: WindowManagerPolicy
-            get() = withCache { WindowManagerPolicy() }
-
-        /** From [android.content.pm.ActivityInfo] */
-        private const val SCREEN_ORIENTATION_LANDSCAPE = 0
-        private const val SCREEN_ORIENTATION_PORTRAIT = 1
-        private const val SCREEN_ORIENTATION_NOSENSOR = 5
-        private const val SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6
-        private const val SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7
-        private const val SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8
-        private const val SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9
-        private const val SCREEN_ORIENTATION_USER_LANDSCAPE = 11
-        private const val SCREEN_ORIENTATION_USER_PORTRAIT = 12
-        private const val SCREEN_ORIENTATION_LOCKED = 14
-
-        @JsName("from")
-        fun from(
-            focusedAppToken: String = "",
-            forceStatusBar: Boolean = false,
-            forceStatusBarFromKeyguard: Boolean = false,
-            keyguardDrawComplete: Boolean = false,
-            keyguardOccluded: Boolean = false,
-            keyguardOccludedChanged: Boolean = false,
-            keyguardOccludedPending: Boolean = false,
-            lastSystemUiFlags: Int = 0,
-            orientation: Int = 0,
-            rotation: PlatformConsts.Rotation = PlatformConsts.Rotation.ROTATION_0,
-            rotationMode: Int = 0,
-            screenOnFully: Boolean = false,
-            windowManagerDrawComplete: Boolean = false
-        ): WindowManagerPolicy = withCache {
-            WindowManagerPolicy(
-                focusedAppToken,
-                forceStatusBar,
-                forceStatusBarFromKeyguard,
-                keyguardDrawComplete,
-                keyguardOccluded,
-                keyguardOccludedChanged,
-                keyguardOccludedPending,
-                lastSystemUiFlags,
-                orientation,
-                rotation,
-                rotationMode,
-                screenOnFully,
-                windowManagerDrawComplete
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt
deleted file mode 100644
index 6ac5489..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowState.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.region.Region
-import kotlin.js.JsName
-
-/**
- * Represents a window in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class WindowState(
-    @JsName("attributes") val attributes: WindowLayoutParams,
-    @JsName("displayId") val displayId: Int,
-    @JsName("stackId") val stackId: Int,
-    @JsName("layer") val layer: Int,
-    @JsName("isSurfaceShown") val isSurfaceShown: Boolean,
-    @JsName("windowType") val windowType: Int,
-    @JsName("requestedSize") val requestedSize: Size,
-    @JsName("surfacePosition") val surfacePosition: Rect?,
-    @JsName("frame") val frame: Rect,
-    @JsName("containingFrame") val containingFrame: Rect,
-    @JsName("parentFrame") val parentFrame: Rect,
-    @JsName("contentFrame") val contentFrame: Rect,
-    @JsName("contentInsets") val contentInsets: Rect,
-    @JsName("surfaceInsets") val surfaceInsets: Rect,
-    @JsName("givenContentInsets") val givenContentInsets: Rect,
-    @JsName("crop") val crop: Rect,
-    windowContainer: WindowContainer,
-    @JsName("isAppWindow") val isAppWindow: Boolean
-) : WindowContainer(windowContainer, getWindowTitle(windowContainer.title)) {
-    override val isVisible: Boolean
-        get() = super.isVisible && attributes.alpha > 0
-
-    override val isFullscreen: Boolean
-        get() = this.attributes.flags.and(FLAG_FULLSCREEN) > 0
-    @JsName("isStartingWindow") val isStartingWindow: Boolean = windowType == WINDOW_TYPE_STARTING
-    @JsName("isExitingWindow") val isExitingWindow: Boolean = windowType == WINDOW_TYPE_EXITING
-    @JsName("isDebuggerWindow") val isDebuggerWindow: Boolean = windowType == WINDOW_TYPE_DEBUGGER
-    @JsName("isValidNavBarType") val isValidNavBarType: Boolean = attributes.isValidNavBarType
-
-    @JsName("frameRegion") val frameRegion: Region = Region.from(frame)
-
-    @JsName("getWindowTypeSuffix")
-    private fun getWindowTypeSuffix(windowType: Int): String =
-        when (windowType) {
-            WINDOW_TYPE_STARTING -> " STARTING"
-            WINDOW_TYPE_EXITING -> " EXITING"
-            WINDOW_TYPE_DEBUGGER -> " DEBUGGER"
-            else -> ""
-        }
-
-    override fun toString(): String =
-        "${this::class.simpleName}: " +
-            "{$token $title${getWindowTypeSuffix(windowType)}} " +
-            "type=${attributes.type} cf=$containingFrame pf=$parentFrame"
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowState) return false
-
-        if (name != other.name) return false
-        if (attributes != other.attributes) return false
-        if (displayId != other.displayId) return false
-        if (stackId != other.stackId) return false
-        if (layer != other.layer) return false
-        if (isSurfaceShown != other.isSurfaceShown) return false
-        if (windowType != other.windowType) return false
-        if (requestedSize != other.requestedSize) return false
-        if (surfacePosition != other.surfacePosition) return false
-        if (frame != other.frame) return false
-        if (containingFrame != other.containingFrame) return false
-        if (parentFrame != other.parentFrame) return false
-        if (contentFrame != other.contentFrame) return false
-        if (contentInsets != other.contentInsets) return false
-        if (surfaceInsets != other.surfaceInsets) return false
-        if (givenContentInsets != other.givenContentInsets) return false
-        if (crop != other.crop) return false
-        if (isAppWindow != other.isAppWindow) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = attributes.hashCode()
-        result = 31 * result + displayId
-        result = 31 * result + stackId
-        result = 31 * result + layer
-        result = 31 * result + isSurfaceShown.hashCode()
-        result = 31 * result + windowType
-        result = 31 * result + frame.hashCode()
-        result = 31 * result + containingFrame.hashCode()
-        result = 31 * result + parentFrame.hashCode()
-        result = 31 * result + contentFrame.hashCode()
-        result = 31 * result + contentInsets.hashCode()
-        result = 31 * result + surfaceInsets.hashCode()
-        result = 31 * result + givenContentInsets.hashCode()
-        result = 31 * result + crop.hashCode()
-        result = 31 * result + isAppWindow.hashCode()
-        result = 31 * result + isStartingWindow.hashCode()
-        result = 31 * result + isExitingWindow.hashCode()
-        result = 31 * result + isDebuggerWindow.hashCode()
-        result = 31 * result + isValidNavBarType.hashCode()
-        result = 31 * result + frameRegion.hashCode()
-        return result
-    }
-
-    companion object {
-        /**
-         * From {@see android.view.WindowManager.FLAG_FULLSCREEN}.
-         *
-         * This class is shared between JVM and JS (Winscope) and cannot access Android internals
-         */
-        @JsName("FLAG_FULLSCREEN") private const val FLAG_FULLSCREEN = 0x00000400
-        @JsName("WINDOW_TYPE_STARTING") internal const val WINDOW_TYPE_STARTING = 1
-        @JsName("WINDOW_TYPE_EXITING") internal const val WINDOW_TYPE_EXITING = 2
-        @JsName("WINDOW_TYPE_DEBUGGER") private const val WINDOW_TYPE_DEBUGGER = 3
-
-        @JsName("STARTING_WINDOW_PREFIX") internal const val STARTING_WINDOW_PREFIX = "Starting "
-        @JsName("DEBUGGER_WINDOW_PREFIX")
-        internal const val DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: "
-
-        @JsName("getWindowTitle")
-        private fun getWindowTitle(title: String): String {
-            return when {
-                // Existing code depends on the prefix being removed
-                title.startsWith(STARTING_WINDOW_PREFIX) ->
-                    title.substring(STARTING_WINDOW_PREFIX.length)
-                title.startsWith(DEBUGGER_WINDOW_PREFIX) ->
-                    title.substring(DEBUGGER_WINDOW_PREFIX.length)
-                else -> title
-            }
-        }
-
-        fun withTitle(title: String): WindowState {
-            val windowContainer = WindowContainer.withTitle(title)
-            return WindowState(
-                WindowLayoutParams.EMPTY,
-                0,
-                0,
-                0,
-                false,
-                0,
-                Size.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                Rect.EMPTY,
-                windowContainer,
-                false
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt b/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt
deleted file mode 100644
index 872de8f..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/common/windowmanager/windows/WindowToken.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.common.windowmanager.windows
-
-/**
- * Represents a window token in the window manager hierarchy
- *
- * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
- * Java/Android functionality
- */
-class WindowToken(windowContainer: WindowContainer) : WindowContainer(windowContainer) {
-    override val isVisible: Boolean
-        get() = false
-    override fun toString(): String {
-        return "${this::class.simpleName}: {$token $title}"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WindowToken) return false
-        if (!super.equals(other)) return false
-
-        if (isVisible != other.isVisible) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = super.hashCode()
-        result = 31 * result + isVisible.hashCode()
-        return result
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/AbstractTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/AbstractTraceParser.kt
deleted file mode 100644
index 0ddb067..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/AbstractTraceParser.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.traces.parser
-
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.Utils
-import com.android.server.wm.traces.common.parser.AbstractParser
-
-/** Base trace parser class */
-abstract class AbstractTraceParser<
-    InputTypeTrace, InputTypeEntry, OutputTypeEntry, OutputTypeTrace> :
-    AbstractParser<InputTypeTrace, OutputTypeTrace>() {
-    protected abstract fun onBeforeParse(input: InputTypeTrace)
-    protected abstract fun getEntries(input: InputTypeTrace): List<InputTypeEntry>
-    protected abstract fun getTimestamp(entry: InputTypeEntry): Timestamp
-    protected abstract fun doParseEntry(entry: InputTypeEntry): OutputTypeEntry
-    protected abstract fun createTrace(entries: List<OutputTypeEntry>): OutputTypeTrace
-
-    open fun shouldParseEntry(entry: InputTypeEntry) = true
-
-    override fun parse(bytes: ByteArray, clearCache: Boolean): OutputTypeTrace {
-        return parse(
-            bytes,
-            from = CrossPlatform.timestamp.min(),
-            to = CrossPlatform.timestamp.max(),
-            addInitialEntry = true,
-            clearCache = clearCache
-        )
-    }
-
-    override fun parse(input: InputTypeTrace, clearCache: Boolean): OutputTypeTrace {
-        return parse(
-            input,
-            from = CrossPlatform.timestamp.min(),
-            to = CrossPlatform.timestamp.max(),
-            addInitialEntry = true,
-            clearCache = clearCache
-        )
-    }
-
-    override fun doParse(input: InputTypeTrace): OutputTypeTrace {
-        return doParse(
-            input,
-            from = CrossPlatform.timestamp.min(),
-            to = CrossPlatform.timestamp.max(),
-            addInitialEntry = true
-        )
-    }
-
-    /**
-     * Uses [InputTypeTrace] to generates a trace
-     *
-     * @param input Parsed proto data
-     * @param from Initial timestamp to be parsed
-     * @param to Final timestamp to be parsed
-     * @param addInitialEntry If the last entry smaller than [from] should be included as well
-     */
-    private fun doParse(
-        input: InputTypeTrace,
-        from: Timestamp,
-        to: Timestamp,
-        addInitialEntry: Boolean
-    ): OutputTypeTrace {
-        onBeforeParse(input)
-        val parsedEntries = mutableListOf<OutputTypeEntry>()
-        val rawEntries = getEntries(input)
-        val allInputTimestamps = rawEntries.map { getTimestamp(it) }
-        val selectedInputTimestamps =
-            Utils.getTimestampsInRange(allInputTimestamps, from, to, addInitialEntry)
-        for (rawEntry in rawEntries) {
-            val currTimestamp = getTimestamp(rawEntry)
-            if (!selectedInputTimestamps.contains(currTimestamp) || !shouldParseEntry(rawEntry)) {
-                continue
-            }
-            val parsedEntry =
-                CrossPlatform.log.withTracing("doParseEntry") { doParseEntry(rawEntry) }
-            parsedEntries.add(parsedEntry)
-        }
-        return createTrace(parsedEntries)
-    }
-
-    /**
-     * Uses [InputTypeTrace] to generates a trace
-     *
-     * @param input Parsed proto data
-     * @param from Initial timestamp to be parsed
-     * @param to Final timestamp to be parsed
-     * @param addInitialEntry If the last entry smaller than [from] should be included as well
-     * @param clearCache If the caching used while parsing the object should be cleared
-     */
-    fun parse(
-        input: InputTypeTrace,
-        from: Timestamp,
-        to: Timestamp,
-        addInitialEntry: Boolean = true,
-        clearCache: Boolean = true
-    ): OutputTypeTrace {
-        return CrossPlatform.log.withTracing("${this::class.simpleName}#parse") {
-            try {
-                doParse(input, from, to, addInitialEntry)
-            } finally {
-                if (clearCache) {
-                    Cache.clear()
-                }
-            }
-        }
-    }
-
-    /**
-     * Uses a [ByteArray] to generates a trace
-     *
-     * @param bytes Parsed proto data
-     * @param from Initial timestamp to be parsed
-     * @param to Final timestamp to be parsed
-     * @param addInitialEntry If the last entry smaller than [from] should be included as well
-     * @param clearCache If the caching used while parsing the object should be cleared
-     */
-    fun parse(
-        bytes: ByteArray,
-        from: Timestamp,
-        to: Timestamp,
-        addInitialEntry: Boolean = true,
-        clearCache: Boolean = true
-    ): OutputTypeTrace {
-        val input = decodeByteArray(bytes)
-        return parse(input, from, to, addInitialEntry, clearCache)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt
deleted file mode 100644
index e7a72ca..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/DeviceDumpParser.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.parser
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.DeviceStateDump
-import com.android.server.wm.traces.common.DeviceTraceDump
-import com.android.server.wm.traces.common.NullableDeviceStateDump
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayersTrace
-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.layers.LayersTraceParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerDumpParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
-
-/**
- * Represents a state dump containing the [WindowManagerTrace] and the [LayersTrace] both parsed and
- * in raw (byte) data.
- */
-class DeviceDumpParser {
-    companion object {
-        /**
-         * Creates a device state dump containing the [WindowManagerTrace] and [LayersTrace]
-         * obtained from a `dumpsys` command. The parsed traces will contain a single
-         * [WindowManagerState] or [BaseLayerTraceEntry].
-         *
-         * @param wmTraceData [WindowManagerTrace] content
-         * @param layersTraceData [LayersTrace] content
-         * @param clearCacheAfterParsing If the caching used while parsing the proto should be
-         * ```
-         *                               cleared or remain in memory
-         * ```
-         */
-        @JvmStatic
-        fun fromNullableDump(
-            wmTraceData: ByteArray,
-            layersTraceData: ByteArray,
-            clearCacheAfterParsing: Boolean
-        ): NullableDeviceStateDump {
-            return CrossPlatform.log.withTracing("fromNullableDump") {
-                NullableDeviceStateDump(
-                    wmState =
-                        if (wmTraceData.isNotEmpty()) {
-                            WindowManagerDumpParser()
-                                .parse(wmTraceData, clearCache = clearCacheAfterParsing)
-                                .first()
-                        } else {
-                            null
-                        },
-                    layerState =
-                        if (layersTraceData.isNotEmpty()) {
-                            LayersTraceParser()
-                                .parse(layersTraceData, clearCache = clearCacheAfterParsing)
-                                .first()
-                        } else {
-                            null
-                        }
-                )
-            }
-        }
-
-        /** See [fromNullableDump] */
-        @JvmStatic
-        fun fromDump(
-            wmTraceData: ByteArray,
-            layersTraceData: ByteArray,
-            clearCacheAfterParsing: Boolean
-        ): DeviceStateDump {
-            return CrossPlatform.log.withTracing("fromDump") {
-                val nullableDump =
-                    fromNullableDump(wmTraceData, layersTraceData, clearCacheAfterParsing)
-                DeviceStateDump(
-                    nullableDump.wmState ?: error("WMState dump missing"),
-                    nullableDump.layerState ?: error("Layer State dump missing")
-                )
-            }
-        }
-
-        /**
-         * Creates a device state dump containing the WindowManager and Layers trace obtained from a
-         * regular trace. The parsed traces may contain a multiple [WindowManagerState] or
-         * [BaseLayerTraceEntry].
-         *
-         * @param wmTraceData [WindowManagerTrace] content
-         * @param layersTraceData [LayersTrace] content
-         * @param clearCache If the caching used while parsing the proto should be
-         * ```
-         *                               cleared or remain in memory
-         * ```
-         */
-        @JvmStatic
-        fun fromTrace(
-            wmTraceData: ByteArray,
-            layersTraceData: ByteArray,
-            clearCache: Boolean
-        ): DeviceTraceDump {
-            return CrossPlatform.log.withTracing("fromTrace") {
-                DeviceTraceDump(
-                    wmTrace =
-                        if (wmTraceData.isNotEmpty()) {
-                            WindowManagerTraceParser().parse(wmTraceData, clearCache = clearCache)
-                        } else {
-                            null
-                        },
-                    layersTrace =
-                        if (layersTraceData.isNotEmpty()) {
-                            LayersTraceParser().parse(layersTraceData, clearCache = clearCache)
-                        } else {
-                            null
-                        }
-                )
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt
deleted file mode 100644
index a4d4fed..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/Extensions.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-@file:JvmName("Extensions")
-
-package com.android.server.wm.traces.parser
-
-import android.app.UiAutomation
-import android.content.ComponentName
-import android.os.ParcelFileDescriptor
-import android.os.Trace
-import android.util.Log
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.DeviceStateDump
-import com.android.server.wm.traces.common.LoggerBuilder
-import com.android.server.wm.traces.common.NullableDeviceStateDump
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-
-internal const val LOG_TAG = "AMWM_FLICKER"
-
-val ANDROID_LOGGER =
-    LoggerBuilder()
-        .setD { tag, msg -> Log.d(tag, msg) }
-        .setI { tag, msg -> Log.i(tag, msg) }
-        .setW { tag, msg -> Log.w(tag, msg) }
-        .setE { tag, msg, error -> Log.e(tag, msg, error) }
-        .setOnTracing { name, predicate ->
-            try {
-                Trace.beginSection(name)
-                predicate()
-            } finally {
-                Trace.endSection()
-            }
-        }
-        .build()
-
-fun Rect.toAndroidRect(): android.graphics.Rect {
-    return android.graphics.Rect(left, top, right, bottom)
-}
-
-fun android.graphics.Rect.toFlickerRect(): Rect {
-    return Rect.from(left, top, right, bottom)
-}
-
-private fun executeCommand(uiAutomation: UiAutomation, cmd: String): ByteArray {
-    val fileDescriptor = uiAutomation.executeShellCommand(cmd)
-    ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { inputStream ->
-        return inputStream.readBytes()
-    }
-}
-
-private fun getCurrentWindowManagerState(uiAutomation: UiAutomation) =
-    executeCommand(uiAutomation, "dumpsys window --proto")
-
-private fun getCurrentLayersState(uiAutomation: UiAutomation) =
-    executeCommand(uiAutomation, "dumpsys SurfaceFlinger --proto")
-
-/**
- * Gets the current device state dump containing the [WindowManagerState] (optional) and the
- * [BaseLayerTraceEntry] (optional) in raw (byte) data.
- *
- * @param dumpFlags Flags determining which types of traces should be included in the dump
- */
-@JvmOverloads
-fun getCurrentState(
-    uiAutomation: UiAutomation,
-    @WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS)
-): Pair<ByteArray, ByteArray> {
-    if (dumpFlags == 0) {
-        throw IllegalArgumentException("No dump specified")
-    }
-
-    CrossPlatform.log.d(LOG_TAG, "Requesting new device state dump")
-    val wmTraceData =
-        if (dumpFlags.and(FLAG_STATE_DUMP_FLAG_WM) > 0) {
-            getCurrentWindowManagerState(uiAutomation)
-        } else {
-            ByteArray(0)
-        }
-    val layersTraceData =
-        if (dumpFlags.and(FLAG_STATE_DUMP_FLAG_LAYERS) > 0) {
-            getCurrentLayersState(uiAutomation)
-        } else {
-            ByteArray(0)
-        }
-
-    return Pair(wmTraceData, layersTraceData)
-}
-
-/**
- * Gets the current device state dump containing the [WindowManagerState] (optional) and the
- * [BaseLayerTraceEntry] (optional) parsed
- *
- * @param dumpFlags Flags determining which types of traces should be included in the dump
- * @param clearCacheAfterParsing If the caching used while parsing the proto should be
- * ```
- *                               cleared or remain in memory
- * ```
- */
-@JvmOverloads
-fun getCurrentStateDumpNullable(
-    uiAutomation: UiAutomation,
-    @WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS),
-    clearCacheAfterParsing: Boolean
-): NullableDeviceStateDump {
-    val currentStateDump = getCurrentState(uiAutomation, dumpFlags)
-    return DeviceDumpParser.fromNullableDump(
-        currentStateDump.first,
-        currentStateDump.second,
-        clearCacheAfterParsing = clearCacheAfterParsing
-    )
-}
-
-fun getCurrentStateDump(
-    uiAutomation: UiAutomation,
-    clearCacheAfterParsing: Boolean
-): DeviceStateDump {
-    val currentStateDump = getCurrentState(uiAutomation)
-    return DeviceDumpParser.fromDump(
-        currentStateDump.first,
-        currentStateDump.second,
-        clearCacheAfterParsing = clearCacheAfterParsing
-    )
-}
-
-/** Converts an Android [ComponentName] into a flicker [ComponentNameMatcher] */
-fun ComponentName.toFlickerComponent(): ComponentNameMatcher =
-    ComponentNameMatcher(this.packageName, this.className)
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/WmStateDumpFlags.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/WmStateDumpFlags.kt
deleted file mode 100644
index 5be839b..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/WmStateDumpFlags.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.parser
-
-import android.support.annotation.IntDef
-
-@IntDef(flag = true, value = [FLAG_STATE_DUMP_FLAG_WM, FLAG_STATE_DUMP_FLAG_LAYERS])
-@Retention(AnnotationRetention.SOURCE)
-annotation class WmStateDumpFlags
-
-/** Include WM trace in the dump */
-const val FLAG_STATE_DUMP_FLAG_WM = 1
-
-/** Include the layers trace ni the dump */
-const val FLAG_STATE_DUMP_FLAG_LAYERS = 2
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt
deleted file mode 100644
index fc47733..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/LayersTraceParser.kt
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2021 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.traces.parser.layers
-
-import android.surfaceflinger.Common
-import android.surfaceflinger.Display
-import android.surfaceflinger.Layers
-import android.surfaceflinger.Layerstrace
-import com.android.server.wm.traces.common.ActiveBuffer
-import com.android.server.wm.traces.common.Color
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.RectF
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.Timestamp
-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.LayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.parser.AbstractTraceParser
-
-/** Parser for [LayersTrace] objects containing traces or state dumps */
-class LayersTraceParser(
-    private val ignoreLayersStackMatchNoDisplay: Boolean = true,
-    private val ignoreLayersInVirtualDisplay: Boolean = true,
-    private val legacyTrace: Boolean = false,
-    private val orphanLayerCallback: ((Layer) -> Boolean)? = null,
-) :
-    AbstractTraceParser<
-        Layerstrace.LayersTraceFileProto, Layerstrace.LayersTraceProto, LayerTraceEntry, LayersTrace
-    >() {
-    private var realToElapsedTimeOffsetNanos = 0L
-
-    override val traceName: String = "Layers Trace"
-
-    override fun doDecodeByteArray(bytes: ByteArray): Layerstrace.LayersTraceFileProto =
-        Layerstrace.LayersTraceFileProto.parseFrom(bytes)
-
-    override fun createTrace(entries: List<LayerTraceEntry>): LayersTrace =
-        LayersTrace(entries.toTypedArray())
-
-    override fun getEntries(
-        input: Layerstrace.LayersTraceFileProto
-    ): List<Layerstrace.LayersTraceProto> = input.entryList
-
-    override fun getTimestamp(entry: Layerstrace.LayersTraceProto): Timestamp {
-        require(legacyTrace || realToElapsedTimeOffsetNanos != 0L)
-        return CrossPlatform.timestamp.from(
-            systemUptimeNanos = entry.elapsedRealtimeNanos,
-            unixNanos = entry.elapsedRealtimeNanos + realToElapsedTimeOffsetNanos
-        )
-    }
-
-    override fun onBeforeParse(input: Layerstrace.LayersTraceFileProto) {
-        realToElapsedTimeOffsetNanos = input.realToElapsedTimeOffsetNanos
-    }
-
-    override fun doParseEntry(entry: Layerstrace.LayersTraceProto): LayerTraceEntry {
-        val layers = entry.layers.layersList.map { newLayer(it) }.toTypedArray()
-        val displays = entry.displaysList.map { newDisplay(it) }.toTypedArray()
-        val builder =
-            LayerTraceEntryBuilder()
-                .setElapsedTimestamp(entry.elapsedRealtimeNanos.toString())
-                .setLayers(layers)
-                .setDisplays(displays)
-                .setVSyncId(entry.vsyncId.toString())
-                .setHwcBlob(entry.hwcBlob)
-                .setWhere(entry.where)
-                .setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNanos.toString())
-                .setOrphanLayerCallback(orphanLayerCallback)
-                .ignoreLayersStackMatchNoDisplay(ignoreLayersStackMatchNoDisplay)
-                .ignoreVirtualDisplay(ignoreLayersInVirtualDisplay)
-        return builder.build()
-    }
-
-    companion object {
-        private fun newLayer(
-            proto: Layers.LayerProto,
-            excludeCompositionState: Boolean = false
-        ): Layer {
-            // Differentiate between the cases when there's no HWC data on
-            // the trace, and when the visible region is actually empty
-            val activeBuffer = proto.activeBuffer.toBuffer()
-            val visibleRegion = proto.visibleRegion.toRegion() ?: Region.EMPTY
-            val crop = proto.crop?.toCropRect()
-            return Layer.from(
-                proto.name ?: "",
-                proto.id,
-                proto.parent,
-                proto.z,
-                visibleRegion,
-                activeBuffer,
-                proto.flags,
-                proto.bounds?.toRectF() ?: RectF.EMPTY,
-                proto.color.toColor(),
-                proto.isOpaque,
-                proto.shadowRadius,
-                proto.cornerRadius,
-                proto.type ?: "",
-                proto.screenBounds?.toRectF() ?: RectF.EMPTY,
-                Transform(proto.transform, proto.position),
-                proto.sourceBounds?.toRectF() ?: RectF.EMPTY,
-                proto.currFrame,
-                proto.effectiveScalingMode,
-                Transform(proto.bufferTransform, position = null),
-                toHwcCompositionType(proto.hwcCompositionType),
-                proto.hwcCrop.toRectF() ?: RectF.EMPTY,
-                proto.hwcFrame.toRect(),
-                proto.backgroundBlurRadius,
-                crop,
-                proto.isRelativeOf,
-                proto.zOrderRelativeOf,
-                proto.layerStack,
-                Transform(proto.transform, position = proto.requestedPosition),
-                proto.requestedColor.toColor(),
-                proto.cornerRadiusCrop?.toRectF() ?: RectF.EMPTY,
-                Transform(proto.inputWindowInfo?.transform, position = null),
-                proto.inputWindowInfo?.touchableRegion?.toRegion(),
-                excludeCompositionState
-            )
-        }
-
-        private fun newDisplay(
-            proto: Display.DisplayProto
-        ): com.android.server.wm.traces.common.layers.Display {
-            return com.android.server.wm.traces.common.layers.Display.from(
-                proto.id.toULong(),
-                proto.name,
-                proto.layerStack,
-                proto.size.toSize(),
-                proto.layerStackSpaceRect.toRect(),
-                Transform(proto.transform, position = null),
-                proto.isVirtual
-            )
-        }
-
-        private fun Layers.FloatRectProto?.toRectF(): RectF? {
-            return this?.let { RectF.from(left = left, top = top, right = right, bottom = bottom) }
-        }
-
-        private fun Common.SizeProto?.toSize(): Size {
-            return this?.let { Size.from(this.w, this.h) } ?: Size.EMPTY
-        }
-
-        private fun Common.ColorProto?.toColor(): Color {
-            return this?.let { Color.from(r, g, b, a) } ?: Color.EMPTY
-        }
-
-        private fun Layers.ActiveBufferProto?.toBuffer(): ActiveBuffer {
-            return this?.let { ActiveBuffer.from(width, height, stride, format) }
-                ?: ActiveBuffer.EMPTY
-        }
-
-        private fun toHwcCompositionType(value: Layers.HwcCompositionType): HwcCompositionType {
-            return when (value) {
-                Layers.HwcCompositionType.INVALID -> HwcCompositionType.INVALID
-                Layers.HwcCompositionType.CLIENT -> HwcCompositionType.CLIENT
-                Layers.HwcCompositionType.DEVICE -> HwcCompositionType.DEVICE
-                Layers.HwcCompositionType.SOLID_COLOR -> HwcCompositionType.SOLID_COLOR
-                Layers.HwcCompositionType.CURSOR -> HwcCompositionType.CURSOR
-                Layers.HwcCompositionType.SIDEBAND -> HwcCompositionType.SIDEBAND
-                else -> HwcCompositionType.UNRECOGNIZED
-            }
-        }
-
-        private fun Common.RectProto?.toCropRect(): Rect? {
-            return when {
-                this == null -> Rect.EMPTY
-                // crop (0,0) (-1,-1) means no crop
-                right == -1 && left == 0 && bottom == -1 && top == 0 -> null
-                (right - left) <= 0 || (bottom - top) <= 0 -> Rect.EMPTY
-                else -> Rect.from(left, top, right, bottom)
-            }
-        }
-
-        /**
-         * Extracts [Rect] from [Common.RegionProto] by returning a rect that encompasses all the
-         * rectangles making up the region.
-         */
-        private fun Common.RegionProto?.toRegion(): Region? {
-            return this?.let {
-                val rectArray = this.rectList.map { it.toRect() }.toTypedArray()
-                return Region(rectArray)
-            }
-        }
-
-        private fun Common.RectProto?.toRect(): Rect =
-            Rect.from(this?.left ?: 0, this?.top ?: 0, this?.right ?: 0, this?.bottom ?: 0)
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt
deleted file mode 100644
index 8870c58..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/layers/Transform.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.parser.layers
-
-import android.surfaceflinger.Common.TransformProto
-import android.surfaceflinger.Layers
-import com.android.server.wm.traces.common.Matrix33
-import com.android.server.wm.traces.common.layers.Transform
-import com.android.server.wm.traces.common.layers.Transform.Companion.FLIP_H_VAL
-import com.android.server.wm.traces.common.layers.Transform.Companion.FLIP_V_VAL
-import com.android.server.wm.traces.common.layers.Transform.Companion.ROTATE_VAL
-import com.android.server.wm.traces.common.layers.Transform.Companion.ROT_90_VAL
-import com.android.server.wm.traces.common.layers.Transform.Companion.SCALE_VAL
-import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagClear
-import com.android.server.wm.traces.common.layers.Transform.Companion.isFlagSet
-
-fun Transform(transform: TransformProto?, position: Layers.PositionProto?) =
-    Transform.from(transform?.type, getMatrix(transform, position))
-
-private fun getMatrix(transform: TransformProto?, position: Layers.PositionProto?): Matrix33 {
-    val x = position?.x ?: 0f
-    val y = position?.y ?: 0f
-
-    return when {
-        transform == null || Transform.isSimpleTransform(transform.type) ->
-            transform?.type.getDefaultTransform(x, y)
-        else -> Matrix33.from(transform.dsdx, transform.dtdx, x, transform.dsdy, transform.dtdy, y)
-    }
-}
-
-private fun Int?.getDefaultTransform(x: Float, y: Float): Matrix33 {
-    return when {
-        // IDENTITY
-        this == null -> Matrix33.identity(x, y)
-        // // ROT_270 = ROT_90|FLIP_H|FLIP_V
-        isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> Matrix33.rot270(x, y)
-        // ROT_180 = FLIP_H|FLIP_V
-        isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> Matrix33.rot180(x, y)
-        // ROT_90
-        isFlagSet(ROT_90_VAL) -> Matrix33.rot90(x, y)
-        // IDENTITY
-        isFlagClear(SCALE_VAL or ROTATE_VAL) -> Matrix33.identity(x, y)
-        else -> throw IllegalStateException("Unknown transform type $this")
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/transaction/TransactionsTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/transaction/TransactionsTraceParser.kt
deleted file mode 100644
index 225b155..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/transaction/TransactionsTraceParser.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.traces.parser.transaction
-
-import android.surfaceflinger.proto.Transactions
-import android.surfaceflinger.proto.Transactions.TransactionState
-import android.surfaceflinger.proto.Transactions.TransactionTraceFile
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.transactions.Transaction
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transactions.TransactionsTraceEntry
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.parser.AbstractTraceParser
-
-/** Parser for [TransitionsTrace] objects */
-class TransactionsTraceParser :
-    AbstractTraceParser<
-        TransactionTraceFile,
-        Transactions.TransactionTraceEntry,
-        TransactionsTraceEntry,
-        TransactionsTrace
-    >() {
-    private var timestampOffset = 0L
-    override val traceName: String = "Transactions trace"
-
-    override fun onBeforeParse(input: TransactionTraceFile) {
-        timestampOffset = input.realToElapsedTimeOffsetNanos
-    }
-
-    override fun getTimestamp(entry: Transactions.TransactionTraceEntry): Timestamp {
-        require(timestampOffset != 0L)
-        return CrossPlatform.timestamp.from(
-            elapsedNanos = entry.elapsedRealtimeNanos,
-            unixNanos = entry.elapsedRealtimeNanos + timestampOffset
-        )
-    }
-
-    override fun createTrace(entries: List<TransactionsTraceEntry>): TransactionsTrace =
-        TransactionsTrace(entries.toTypedArray())
-
-    override fun getEntries(input: TransactionTraceFile): List<Transactions.TransactionTraceEntry> =
-        input.entryList
-
-    override fun doDecodeByteArray(bytes: ByteArray): TransactionTraceFile =
-        TransactionTraceFile.parseFrom(bytes)
-
-    override fun doParseEntry(entry: Transactions.TransactionTraceEntry): TransactionsTraceEntry {
-        val transactions = parseTransactionsProto(entry.transactionsList)
-        val transactionsTraceEntry =
-            TransactionsTraceEntry(
-                CrossPlatform.timestamp.from(
-                    elapsedNanos = entry.elapsedRealtimeNanos,
-                    elapsedOffsetNanos = timestampOffset
-                ),
-                entry.vsyncId,
-                transactions
-            )
-        transactions.forEach { it.appliedInEntry = transactionsTraceEntry }
-        return transactionsTraceEntry
-    }
-
-    private fun parseTransactionsProto(
-        transactionStates: List<TransactionState>
-    ): Array<Transaction> {
-        val transactions = mutableListOf<Transaction>()
-        for (state in transactionStates) {
-            val transaction =
-                Transaction(
-                    state.pid,
-                    state.uid,
-                    state.vsyncId,
-                    state.postTime,
-                    state.transactionId
-                )
-            transactions.add(transaction)
-        }
-
-        return transactions.toTypedArray()
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/transition/TransitionsTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/transition/TransitionsTraceParser.kt
deleted file mode 100644
index a8d28a6..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/transition/TransitionsTraceParser.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.traces.parser.transition
-
-import com.android.server.wm.shell.nano.TransitionTraceProto
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.WindowingMode
-import com.android.server.wm.traces.common.transition.Transition
-import com.android.server.wm.traces.common.transition.Transition.Companion.Type
-import com.android.server.wm.traces.common.transition.TransitionChange
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.parser.AbstractTraceParser
-
-/** Parser for [TransitionsTrace] objects */
-class TransitionsTraceParser :
-    AbstractTraceParser<
-        TransitionTraceProto,
-        com.android.server.wm.shell.nano.Transition,
-        Transition,
-        TransitionsTrace
-    >() {
-    override val traceName: String = "Transition trace"
-
-    override fun createTrace(entries: List<Transition>): TransitionsTrace {
-        return TransitionsTrace(entries.toTypedArray())
-    }
-
-    override fun doDecodeByteArray(bytes: ByteArray): TransitionTraceProto =
-        TransitionTraceProto.parseFrom(bytes)
-
-    override fun shouldParseEntry(entry: com.android.server.wm.shell.nano.Transition): Boolean {
-        return true
-    }
-
-    override fun getEntries(
-        input: TransitionTraceProto
-    ): List<com.android.server.wm.shell.nano.Transition> = input.sentTransitions.toList()
-
-    override fun getTimestamp(entry: com.android.server.wm.shell.nano.Transition): Timestamp {
-        return CrossPlatform.timestamp.from(elapsedNanos = entry.createTimeNs)
-    }
-
-    override fun onBeforeParse(input: TransitionTraceProto) {}
-
-    override fun doParseEntry(entry: com.android.server.wm.shell.nano.Transition): Transition {
-        val windowingMode = WindowingMode.WINDOWING_MODE_UNDEFINED // TODO: Get the windowing mode
-        val changes =
-            entry.targets.map {
-                TransitionChange(Type.fromInt(it.mode), it.layerId, it.windowId, windowingMode)
-            }
-
-        return Transition(
-            start = CrossPlatform.timestamp.from(elapsedNanos = entry.createTimeNs),
-            sendTime = CrossPlatform.timestamp.from(elapsedNanos = entry.sendTimeNs),
-            startTransactionId = entry.startTransactionId,
-            finishTransactionId = entry.finishTransactionId,
-            changes = changes,
-            played = true,
-            aborted = false,
-        )
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt
deleted file mode 100644
index 5d813da..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WaitForValidActivityState.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.parser.windowmanager
-
-import android.app.ActivityTaskManager
-import android.app.WindowConfiguration
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-
-data class WaitForValidActivityState(
-    @JvmField val activityMatcher: IComponentMatcher?,
-    @JvmField val windowIdentifier: String?,
-    @JvmField val stackId: Int,
-    @JvmField val windowingMode: Int,
-    @JvmField val activityType: Int
-) {
-    constructor(
-        activityName: IComponentMatcher
-    ) : this(
-        activityName,
-        windowIdentifier = activityName.toWindowIdentifier(),
-        stackId = ActivityTaskManager.INVALID_STACK_ID,
-        windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED,
-        activityType = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
-    )
-
-    private constructor(
-        builder: Builder
-    ) : this(
-        activityMatcher = builder.activityName,
-        windowIdentifier = builder.windowIdentifier,
-        stackId = builder.stackId,
-        windowingMode = builder.windowingMode,
-        activityType = builder.activityType
-    )
-
-    override fun toString(): String {
-        val sb = StringBuilder("wait:")
-        if (activityMatcher != null) {
-            sb.append(" activity=").append(activityMatcher.toActivityIdentifier())
-        }
-        if (activityType != WindowConfiguration.ACTIVITY_TYPE_UNDEFINED) {
-            sb.append(" type=").append(activityTypeName(activityType))
-        }
-        if (windowIdentifier != null) {
-            sb.append(" window=").append(windowIdentifier)
-        }
-        if (windowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-            sb.append(" mode=").append(windowingModeName(windowingMode))
-        }
-        if (stackId != ActivityTaskManager.INVALID_STACK_ID) {
-            sb.append(" stack=").append(stackId)
-        }
-        return sb.toString()
-    }
-
-    class Builder constructor(internal var activityName: IComponentMatcher? = null) {
-        internal var windowIdentifier: String? = activityName?.toWindowIdentifier()
-        internal var stackId = ActivityTaskManager.INVALID_STACK_ID
-        internal var windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED
-        internal var activityType = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
-
-        fun setWindowIdentifier(windowIdentifier: String): Builder {
-            this.windowIdentifier = windowIdentifier
-            return this
-        }
-
-        fun setStackId(stackId: Int): Builder {
-            this.stackId = stackId
-            return this
-        }
-
-        fun setWindowingMode(windowingMode: Int): Builder {
-            this.windowingMode = windowingMode
-            return this
-        }
-
-        fun setActivityType(activityType: Int): Builder {
-            this.activityType = activityType
-            return this
-        }
-
-        fun build(): WaitForValidActivityState {
-            return WaitForValidActivityState(this)
-        }
-    }
-
-    companion object {
-        @JvmStatic
-        fun forWindow(windowName: String): WaitForValidActivityState {
-            return Builder().setWindowIdentifier(windowName).build()
-        }
-
-        private fun windowingModeName(windowingMode: Int): String {
-            return when (windowingMode) {
-                WindowConfiguration.WINDOWING_MODE_UNDEFINED -> "UNDEFINED"
-                WindowConfiguration.WINDOWING_MODE_FULLSCREEN -> "FULLSCREEN"
-                WindowConfiguration.WINDOWING_MODE_PINNED -> "PINNED"
-                WindowConfiguration.WINDOWING_MODE_FREEFORM -> "FREEFORM"
-                else -> throw IllegalArgumentException("Unknown WINDOWING_MODE_: $windowingMode")
-            }
-        }
-
-        private fun activityTypeName(activityType: Int): String {
-            return when (activityType) {
-                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED -> "UNDEFINED"
-                WindowConfiguration.ACTIVITY_TYPE_STANDARD -> "STANDARD"
-                WindowConfiguration.ACTIVITY_TYPE_HOME -> "HOME"
-                WindowConfiguration.ACTIVITY_TYPE_RECENTS -> "RECENTS"
-                WindowConfiguration.ACTIVITY_TYPE_ASSISTANT -> "ASSISTANT"
-                else -> throw IllegalArgumentException("Unknown ACTIVITY_TYPE_: $activityType")
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerDumpParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerDumpParser.kt
deleted file mode 100644
index 92c2ef2..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerDumpParser.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.traces.parser.windowmanager
-
-import com.android.server.wm.nano.WindowManagerServiceDumpProto
-import com.android.server.wm.traces.common.parser.AbstractParser
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-/** Parser for [WindowManagerTrace] objects containing dumps */
-class WindowManagerDumpParser :
-    AbstractParser<WindowManagerServiceDumpProto, WindowManagerTrace>() {
-    override val traceName: String = "WM Dump"
-
-    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerServiceDumpProto =
-        WindowManagerServiceDumpProto.parseFrom(bytes)
-
-    override fun doParse(input: WindowManagerServiceDumpProto): WindowManagerTrace {
-        val parsedEntry = WindowManagerStateBuilder().forProto(input).build()
-        return WindowManagerTrace(arrayOf(parsedEntry))
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateBuilder.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateBuilder.kt
deleted file mode 100644
index 150197e..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateBuilder.kt
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * 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.traces.parser.windowmanager
-
-import android.app.nano.WindowConfigurationProto
-import android.content.nano.ConfigurationProto
-import android.graphics.nano.RectProto
-import android.view.Surface
-import android.view.nano.DisplayCutoutProto
-import android.view.nano.ViewProtoEnums
-import android.view.nano.WindowLayoutParamsProto
-import com.android.server.wm.nano.ActivityRecordProto
-import com.android.server.wm.nano.AppTransitionProto
-import com.android.server.wm.nano.ConfigurationContainerProto
-import com.android.server.wm.nano.DisplayAreaProto
-import com.android.server.wm.nano.DisplayContentProto
-import com.android.server.wm.nano.KeyguardControllerProto
-import com.android.server.wm.nano.RootWindowContainerProto
-import com.android.server.wm.nano.TaskFragmentProto
-import com.android.server.wm.nano.TaskProto
-import com.android.server.wm.nano.WindowContainerChildProto
-import com.android.server.wm.nano.WindowContainerProto
-import com.android.server.wm.nano.WindowManagerPolicyProto
-import com.android.server.wm.nano.WindowManagerServiceDumpProto
-import com.android.server.wm.nano.WindowStateProto
-import com.android.server.wm.nano.WindowTokenProto
-import com.android.server.wm.traces.common.Insets
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTraceEntryBuilder
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.Configuration
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayArea
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
-import com.android.server.wm.traces.common.windowmanager.windows.DisplayCutout
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.Task
-import com.android.server.wm.traces.common.windowmanager.windows.TaskFragment
-import com.android.server.wm.traces.common.windowmanager.windows.WindowConfiguration
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowLayoutParams
-import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.common.windowmanager.windows.WindowToken
-
-/** Helper class to create a new WM state */
-class WindowManagerStateBuilder {
-    private var computedZCounter = 0
-    private var realToElapsedTimeOffsetNanos = 0L
-    private var where = ""
-    private var timestamp = 0L
-    private var proto: WindowManagerServiceDumpProto? = null
-
-    fun withRealTimeOffset(value: Long) = apply { realToElapsedTimeOffsetNanos = value }
-
-    fun atPlace(_where: String) = apply { where = _where }
-
-    fun forTimestamp(value: Long) = apply { timestamp = value }
-
-    fun forProto(value: WindowManagerServiceDumpProto) = apply { proto = value }
-
-    fun build(): WindowManagerState {
-        val proto = proto
-        requireNotNull(proto) { "Proto object not specified" }
-
-        computedZCounter = 0
-        return WindowManagerTraceEntryBuilder(
-                _elapsedTimestamp = timestamp.toString(),
-                policy = createWindowManagerPolicy(proto.policy),
-                focusedApp = proto.focusedApp,
-                focusedDisplayId = proto.focusedDisplayId,
-                focusedWindow = proto.focusedWindow?.title ?: "",
-                inputMethodWindowAppToken =
-                    if (proto.inputMethodWindow != null) {
-                        Integer.toHexString(proto.inputMethodWindow.hashCode)
-                    } else {
-                        ""
-                    },
-                isHomeRecentsComponent = proto.rootWindowContainer.isHomeRecentsComponent,
-                isDisplayFrozen = proto.displayFrozen,
-                pendingActivities =
-                    proto.rootWindowContainer.pendingActivities.map { it.title }.toTypedArray(),
-                root = createRootWindowContainer(proto.rootWindowContainer),
-                keyguardControllerState =
-                    createKeyguardControllerState(proto.rootWindowContainer.keyguardController),
-                where = where,
-                realToElapsedTimeOffsetNs = realToElapsedTimeOffsetNanos.toString()
-            )
-            .build()
-    }
-
-    private fun createWindowManagerPolicy(proto: WindowManagerPolicyProto): WindowManagerPolicy {
-        return WindowManagerPolicy.from(
-            focusedAppToken = proto.focusedAppToken ?: "",
-            forceStatusBar = proto.forceStatusBar,
-            forceStatusBarFromKeyguard = proto.forceStatusBarFromKeyguard,
-            keyguardDrawComplete = proto.keyguardDrawComplete,
-            keyguardOccluded = proto.keyguardOccluded,
-            keyguardOccludedChanged = proto.keyguardOccludedChanged,
-            keyguardOccludedPending = proto.keyguardOccludedPending,
-            lastSystemUiFlags = proto.lastSystemUiFlags,
-            orientation = proto.orientation,
-            rotation = PlatformConsts.Rotation.getByValue(proto.rotation),
-            rotationMode = proto.rotationMode,
-            screenOnFully = proto.screenOnFully,
-            windowManagerDrawComplete = proto.windowManagerDrawComplete
-        )
-    }
-
-    private fun createRootWindowContainer(proto: RootWindowContainerProto): RootWindowContainer {
-        return RootWindowContainer(
-            createWindowContainer(
-                proto.windowContainer,
-                proto.windowContainer.children.mapNotNull { p ->
-                    createWindowContainerChild(p, isActivityInTree = false)
-                }
-            )
-                ?: error("Window container should not be null")
-        )
-    }
-
-    private fun createKeyguardControllerState(
-        proto: KeyguardControllerProto?
-    ): KeyguardControllerState {
-        return KeyguardControllerState.from(
-            isAodShowing = proto?.aodShowing ?: false,
-            isKeyguardShowing = proto?.keyguardShowing ?: false,
-            keyguardOccludedStates =
-                proto?.keyguardOccludedStates?.associate { it.displayId to it.keyguardOccluded }
-                    ?: emptyMap()
-        )
-    }
-
-    private fun createWindowContainerChild(
-        proto: WindowContainerChildProto,
-        isActivityInTree: Boolean
-    ): WindowContainer? {
-        return createDisplayContent(proto.displayContent, isActivityInTree)
-            ?: createDisplayArea(proto.displayArea, isActivityInTree)
-                ?: createTask(proto.task, isActivityInTree)
-                ?: createTaskFragment(proto.taskFragment, isActivityInTree)
-                ?: createActivity(proto.activity)
-                ?: createWindowToken(proto.windowToken, isActivityInTree)
-                ?: createWindowState(proto.window, isActivityInTree)
-                ?: createWindowContainer(proto.windowContainer, children = emptyList())
-    }
-
-    private fun createDisplayContent(
-        proto: DisplayContentProto?,
-        isActivityInTree: Boolean
-    ): DisplayContent? {
-        return if (proto == null) {
-            null
-        } else {
-            DisplayContent(
-                id = proto.id,
-                focusedRootTaskId = proto.focusedRootTaskId,
-                resumedActivity = proto.resumedActivity?.title ?: "",
-                singleTaskInstance = proto.singleTaskInstance,
-                defaultPinnedStackBounds = proto.pinnedTaskController?.defaultBounds?.toRect()
-                        ?: Rect.EMPTY,
-                pinnedStackMovementBounds = proto.pinnedTaskController?.movementBounds?.toRect()
-                        ?: Rect.EMPTY,
-                displayRect =
-                    Rect.from(
-                        0,
-                        0,
-                        proto.displayInfo?.logicalWidth ?: 0,
-                        proto.displayInfo?.logicalHeight ?: 0
-                    ),
-                appRect =
-                    Rect.from(
-                        0,
-                        0,
-                        proto.displayInfo?.appWidth ?: 0,
-                        proto.displayInfo?.appHeight ?: 0
-                    ),
-                dpi = proto.dpi,
-                flags = proto.displayInfo?.flags ?: 0,
-                stableBounds = proto.displayFrames?.stableBounds?.toRect() ?: Rect.EMPTY,
-                surfaceSize = proto.surfaceSize,
-                focusedApp = proto.focusedApp,
-                lastTransition =
-                    appTransitionToString(proto.appTransition?.lastUsedAppTransition ?: 0),
-                appTransitionState = appStateToString(proto.appTransition?.appTransitionState ?: 0),
-                rotation =
-                    PlatformConsts.Rotation.getByValue(
-                        proto.displayRotation?.rotation ?: Surface.ROTATION_0
-                    ),
-                lastOrientation = proto.displayRotation?.lastOrientation ?: 0,
-                cutout = createDisplayCutout(proto.displayInfo?.cutout),
-                windowContainer =
-                    createWindowContainer(
-                        proto.rootDisplayArea.windowContainer,
-                        proto.rootDisplayArea.windowContainer.children.mapNotNull { p ->
-                            createWindowContainerChild(p, isActivityInTree)
-                        },
-                        nameOverride = proto.displayInfo?.name ?: ""
-                    )
-                        ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createDisplayArea(
-        proto: DisplayAreaProto?,
-        isActivityInTree: Boolean
-    ): DisplayArea? {
-        return if (proto == null) {
-            null
-        } else {
-            DisplayArea(
-                isTaskDisplayArea = proto.isTaskDisplayArea,
-                windowContainer =
-                    createWindowContainer(
-                        proto.windowContainer,
-                        proto.windowContainer.children.mapNotNull { p ->
-                            createWindowContainerChild(p, isActivityInTree)
-                        }
-                    )
-                        ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createTask(proto: TaskProto?, isActivityInTree: Boolean): Task? {
-        return if (proto == null) {
-            null
-        } else {
-            Task(
-                activityType = proto.taskFragment?.activityType ?: proto.activityType,
-                isFullscreen = proto.fillsParent,
-                bounds = proto.bounds.toRect(),
-                taskId = proto.id,
-                rootTaskId = proto.rootTaskId,
-                displayId = proto.taskFragment?.displayId ?: proto.displayId,
-                lastNonFullscreenBounds = proto.lastNonFullscreenBounds?.toRect() ?: Rect.EMPTY,
-                realActivity = proto.realActivity,
-                origActivity = proto.origActivity,
-                resizeMode = proto.resizeMode,
-                _resumedActivity = proto.resumedActivity?.title ?: "",
-                animatingBounds = proto.animatingBounds,
-                surfaceWidth = proto.surfaceWidth,
-                surfaceHeight = proto.surfaceHeight,
-                createdByOrganizer = proto.createdByOrganizer,
-                minWidth = proto.taskFragment?.minWidth ?: proto.minWidth,
-                minHeight = proto.taskFragment?.minHeight ?: proto.minHeight,
-                windowContainer =
-                    createWindowContainer(
-                        proto.taskFragment?.windowContainer ?: proto.windowContainer,
-                        if (proto.taskFragment != null) {
-                            proto.taskFragment.windowContainer.children.mapNotNull { p ->
-                                createWindowContainerChild(p, isActivityInTree)
-                            }
-                        } else {
-                            proto.windowContainer.children.mapNotNull { p ->
-                                createWindowContainerChild(p, isActivityInTree)
-                            }
-                        }
-                    )
-                        ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createTaskFragment(
-        proto: TaskFragmentProto?,
-        isActivityInTree: Boolean
-    ): TaskFragment? {
-        return if (proto == null) {
-            null
-        } else {
-            TaskFragment(
-                activityType = proto.activityType,
-                displayId = proto.displayId,
-                minWidth = proto.minWidth,
-                minHeight = proto.minHeight,
-                windowContainer =
-                    createWindowContainer(
-                        proto.windowContainer,
-                        proto.windowContainer.children.mapNotNull { p ->
-                            createWindowContainerChild(p, isActivityInTree)
-                        }
-                    )
-                        ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createActivity(proto: ActivityRecordProto?): Activity? {
-        return if (proto == null) {
-            null
-        } else {
-            Activity(
-                name = proto.name,
-                state = proto.state,
-                visible = proto.visible,
-                frontOfTask = proto.frontOfTask,
-                procId = proto.procId,
-                isTranslucent = proto.translucent,
-                windowContainer =
-                    createWindowContainer(
-                        proto.windowToken.windowContainer,
-                        proto.windowToken.windowContainer.children.mapNotNull { p ->
-                            createWindowContainerChild(p, isActivityInTree = true)
-                        }
-                    )
-                        ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createWindowToken(
-        proto: WindowTokenProto?,
-        isActivityInTree: Boolean
-    ): WindowToken? {
-        return if (proto == null) {
-            null
-        } else {
-            WindowToken(
-                createWindowContainer(
-                    proto.windowContainer,
-                    proto.windowContainer.children.mapNotNull { p ->
-                        createWindowContainerChild(p, isActivityInTree)
-                    }
-                )
-                    ?: error("Window container should not be null")
-            )
-        }
-    }
-
-    private fun createWindowState(
-        proto: WindowStateProto?,
-        isActivityInTree: Boolean
-    ): WindowState? {
-        return if (proto == null) {
-            null
-        } else {
-            val identifierName = proto.windowContainer.identifier?.title ?: ""
-            WindowState(
-                attributes = createWindowLayerParams(proto.attributes),
-                displayId = proto.displayId,
-                stackId = proto.stackId,
-                layer = proto.animator?.surface?.layer ?: 0,
-                isSurfaceShown = proto.animator?.surface?.shown ?: false,
-                windowType =
-                    when {
-                        identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX) ->
-                            WindowState.WINDOW_TYPE_STARTING
-                        proto.animatingExit -> WindowState.WINDOW_TYPE_EXITING
-                        identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX) ->
-                            WindowState.WINDOW_TYPE_STARTING
-                        else -> 0
-                    },
-                requestedSize = Size.from(proto.requestedWidth, proto.requestedHeight),
-                surfacePosition = proto.surfacePosition?.toRect(),
-                frame = proto.windowFrames?.frame?.toRect() ?: Rect.EMPTY,
-                containingFrame = proto.windowFrames?.containingFrame?.toRect() ?: Rect.EMPTY,
-                parentFrame = proto.windowFrames?.parentFrame?.toRect() ?: Rect.EMPTY,
-                contentFrame = proto.windowFrames?.contentFrame?.toRect() ?: Rect.EMPTY,
-                contentInsets = proto.windowFrames?.contentInsets?.toRect() ?: Rect.EMPTY,
-                surfaceInsets = proto.surfaceInsets?.toRect() ?: Rect.EMPTY,
-                givenContentInsets = proto.givenContentInsets?.toRect() ?: Rect.EMPTY,
-                crop = proto.animator?.lastClipRect?.toRect() ?: Rect.EMPTY,
-                windowContainer =
-                    createWindowContainer(
-                        proto.windowContainer,
-                        proto.windowContainer.children.mapNotNull { p ->
-                            createWindowContainerChild(p, isActivityInTree)
-                        },
-                        nameOverride =
-                            when {
-                                // Existing code depends on the prefix being removed
-                                identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX) ->
-                                    identifierName.substring(
-                                        WindowState.STARTING_WINDOW_PREFIX.length
-                                    )
-                                identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX) ->
-                                    identifierName.substring(
-                                        WindowState.DEBUGGER_WINDOW_PREFIX.length
-                                    )
-                                else -> identifierName
-                            }
-                    )
-                        ?: error("Window container should not be null"),
-                isAppWindow = isActivityInTree
-            )
-        }
-    }
-
-    private fun createWindowLayerParams(proto: WindowLayoutParamsProto?): WindowLayoutParams {
-        return WindowLayoutParams.from(
-            type = proto?.type ?: 0,
-            x = proto?.x ?: 0,
-            y = proto?.y ?: 0,
-            width = proto?.width ?: 0,
-            height = proto?.height ?: 0,
-            horizontalMargin = proto?.horizontalMargin ?: 0f,
-            verticalMargin = proto?.verticalMargin ?: 0f,
-            gravity = proto?.gravity ?: 0,
-            softInputMode = proto?.softInputMode ?: 0,
-            format = proto?.format ?: 0,
-            windowAnimations = proto?.windowAnimations ?: 0,
-            alpha = proto?.alpha ?: 0f,
-            screenBrightness = proto?.screenBrightness ?: 0f,
-            buttonBrightness = proto?.buttonBrightness ?: 0f,
-            rotationAnimation = proto?.rotationAnimation ?: 0,
-            preferredRefreshRate = proto?.preferredRefreshRate ?: 0f,
-            preferredDisplayModeId = proto?.preferredDisplayModeId ?: 0,
-            hasSystemUiListeners = proto?.hasSystemUiListeners ?: false,
-            inputFeatureFlags = proto?.inputFeatureFlags ?: 0,
-            userActivityTimeout = proto?.userActivityTimeout ?: 0,
-            colorMode = proto?.colorMode ?: 0,
-            flags = proto?.flags ?: 0,
-            privateFlags = proto?.privateFlags ?: 0,
-            systemUiVisibilityFlags = proto?.systemUiVisibilityFlags ?: 0,
-            subtreeSystemUiVisibilityFlags = proto?.subtreeSystemUiVisibilityFlags ?: 0,
-            appearance = proto?.appearance ?: 0,
-            behavior = proto?.behavior ?: 0,
-            fitInsetsTypes = proto?.fitInsetsTypes ?: 0,
-            fitInsetsSides = proto?.fitInsetsSides ?: 0,
-            fitIgnoreVisibility = proto?.fitIgnoreVisibility ?: false
-        )
-    }
-
-    private fun createConfigurationContainer(
-        proto: ConfigurationContainerProto?
-    ): ConfigurationContainer {
-        return ConfigurationContainer(
-            overrideConfiguration = createConfiguration(proto?.overrideConfiguration),
-            fullConfiguration = createConfiguration(proto?.fullConfiguration),
-            mergedOverrideConfiguration = createConfiguration(proto?.mergedOverrideConfiguration)
-        )
-    }
-
-    private fun createConfiguration(proto: ConfigurationProto?): Configuration? {
-        return if (proto == null) {
-            null
-        } else {
-            Configuration.from(
-                windowConfiguration =
-                    if (proto.windowConfiguration != null) {
-                        createWindowConfiguration(proto.windowConfiguration)
-                    } else {
-                        null
-                    },
-                densityDpi = proto.densityDpi,
-                orientation = proto.orientation,
-                screenHeightDp = proto.screenHeightDp,
-                screenWidthDp = proto.screenWidthDp,
-                smallestScreenWidthDp = proto.smallestScreenWidthDp,
-                screenLayout = proto.screenLayout,
-                uiMode = proto.uiMode
-            )
-        }
-    }
-
-    private fun createWindowConfiguration(proto: WindowConfigurationProto): WindowConfiguration {
-        return WindowConfiguration.from(
-            appBounds = proto.appBounds?.toRect(),
-            bounds = proto.bounds?.toRect(),
-            maxBounds = proto.maxBounds?.toRect(),
-            windowingMode = proto.windowingMode,
-            activityType = proto.activityType
-        )
-    }
-
-    private fun createWindowContainer(
-        proto: WindowContainerProto?,
-        children: List<WindowContainer>,
-        nameOverride: String? = null
-    ): WindowContainer? {
-        return if (proto == null) {
-            null
-        } else {
-            WindowContainer(
-                title = nameOverride ?: proto.identifier?.title ?: "",
-                token = proto.identifier?.hashCode?.toString(16) ?: "",
-                orientation = proto.orientation,
-                _isVisible = proto.visible,
-                configurationContainer = createConfigurationContainer(proto.configurationContainer),
-                layerId = proto.surfaceControl?.layerId ?: 0,
-                children = children.toTypedArray(),
-                computedZ = computedZCounter++
-            )
-        }
-    }
-
-    private fun createDisplayCutout(proto: DisplayCutoutProto?): DisplayCutout? {
-        return if (proto == null) {
-            null
-        } else {
-            DisplayCutout(
-                proto.insets?.toInsets() ?: Insets.EMPTY,
-                proto.boundLeft?.toRect() ?: Rect.EMPTY,
-                proto.boundTop?.toRect() ?: Rect.EMPTY,
-                proto.boundRight?.toRect() ?: Rect.EMPTY,
-                proto.boundBottom?.toRect() ?: Rect.EMPTY,
-                proto.waterfallInsets?.toInsets() ?: Insets.EMPTY
-            )
-        }
-    }
-
-    private fun appTransitionToString(transition: Int): String {
-        return when (transition) {
-            ViewProtoEnums.TRANSIT_UNSET -> "TRANSIT_UNSET"
-            ViewProtoEnums.TRANSIT_NONE -> "TRANSIT_NONE"
-            ViewProtoEnums.TRANSIT_ACTIVITY_OPEN -> TRANSIT_ACTIVITY_OPEN
-            ViewProtoEnums.TRANSIT_ACTIVITY_CLOSE -> TRANSIT_ACTIVITY_CLOSE
-            ViewProtoEnums.TRANSIT_TASK_OPEN -> TRANSIT_TASK_OPEN
-            ViewProtoEnums.TRANSIT_TASK_CLOSE -> TRANSIT_TASK_CLOSE
-            ViewProtoEnums.TRANSIT_TASK_TO_FRONT -> "TRANSIT_TASK_TO_FRONT"
-            ViewProtoEnums.TRANSIT_TASK_TO_BACK -> "TRANSIT_TASK_TO_BACK"
-            ViewProtoEnums.TRANSIT_WALLPAPER_CLOSE -> TRANSIT_WALLPAPER_CLOSE
-            ViewProtoEnums.TRANSIT_WALLPAPER_OPEN -> TRANSIT_WALLPAPER_OPEN
-            ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_OPEN -> TRANSIT_WALLPAPER_INTRA_OPEN
-            ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_CLOSE -> TRANSIT_WALLPAPER_INTRA_CLOSE
-            ViewProtoEnums.TRANSIT_TASK_OPEN_BEHIND -> "TRANSIT_TASK_OPEN_BEHIND"
-            ViewProtoEnums.TRANSIT_ACTIVITY_RELAUNCH -> "TRANSIT_ACTIVITY_RELAUNCH"
-            ViewProtoEnums.TRANSIT_DOCK_TASK_FROM_RECENTS -> "TRANSIT_DOCK_TASK_FROM_RECENTS"
-            ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY -> TRANSIT_KEYGUARD_GOING_AWAY
-            ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER ->
-                TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
-            ViewProtoEnums.TRANSIT_KEYGUARD_OCCLUDE -> TRANSIT_KEYGUARD_OCCLUDE
-            ViewProtoEnums.TRANSIT_KEYGUARD_UNOCCLUDE -> TRANSIT_KEYGUARD_UNOCCLUDE
-            ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN -> TRANSIT_TRANSLUCENT_ACTIVITY_OPEN
-            ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE -> TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE
-            ViewProtoEnums.TRANSIT_CRASHING_ACTIVITY_CLOSE -> "TRANSIT_CRASHING_ACTIVITY_CLOSE"
-            else -> error("Invalid lastUsedAppTransition")
-        }
-    }
-
-    private fun appStateToString(appState: Int): String {
-        return when (appState) {
-            AppTransitionProto.APP_STATE_IDLE -> "APP_STATE_IDLE"
-            AppTransitionProto.APP_STATE_READY -> "APP_STATE_READY"
-            AppTransitionProto.APP_STATE_RUNNING -> "APP_STATE_RUNNING"
-            AppTransitionProto.APP_STATE_TIMEOUT -> "APP_STATE_TIMEOUT"
-            else -> error("Invalid AppTransitionState")
-        }
-    }
-
-    private fun RectProto.toRect() = Rect.from(this.left, this.top, this.right, this.bottom)
-
-    private fun RectProto.toInsets() = Insets.from(this.left, this.top, this.right, this.bottom)
-
-    companion object {
-        private const val TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN"
-        private const val TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE"
-        private const val TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN"
-        private const val TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE"
-        private const val TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN"
-        private const val TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE"
-        private const val TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN"
-        private const val TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE"
-        private const val TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY"
-        private const val TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
-            "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER"
-        private const val TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE"
-        private const val TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE"
-        private const val TRANSIT_TRANSLUCENT_ACTIVITY_OPEN = "TRANSIT_TRANSLUCENT_ACTIVITY_OPEN"
-        private const val TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE = "TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE"
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt
deleted file mode 100644
index 05eb9e4..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerStateHelper.kt
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.parser.windowmanager
-
-import android.app.ActivityTaskManager
-import android.app.Instrumentation
-import android.app.WindowConfiguration
-import android.os.SystemClock
-import android.os.Trace
-import android.view.Display
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.traces.common.Condition
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.DeviceStateDump
-import com.android.server.wm.traces.common.WaitCondition
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.IME
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.LAUNCHER
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.SNAPSHOT
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.SPLASH_SCREEN
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.SPLIT_DIVIDER
-import com.android.server.wm.traces.common.component.matchers.IComponentMatcher
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayersTrace
-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.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.common.windowmanager.windows.Activity
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowState
-import com.android.server.wm.traces.parser.LOG_TAG
-import com.android.server.wm.traces.parser.getCurrentStateDump
-
-/** Helper class to wait on [WindowManagerState] or [LayerTraceEntry] conditions */
-open class WindowManagerStateHelper
-@JvmOverloads
-constructor(
-    /** Instrumentation to run the tests */
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-    private val clearCacheAfterParsing: Boolean = true,
-    /** Predicate to supply a new UI information */
-    private val deviceDumpSupplier: () -> DeviceStateDump = {
-        getCurrentStateDump(
-            instrumentation.uiAutomation,
-            clearCacheAfterParsing = clearCacheAfterParsing
-        )
-    },
-    /** Number of attempts to satisfy a wait condition */
-    private val numRetries: Int = WaitCondition.DEFAULT_RETRY_LIMIT,
-    /** Interval between wait for state dumps during wait conditions */
-    private val retryIntervalMs: Long = WaitCondition.DEFAULT_RETRY_INTERVAL_MS
-) {
-    private var internalState: DeviceStateDump? = null
-
-    /** Queries the supplier for a new device state */
-    val currentState: DeviceStateDump
-        get() {
-            if (internalState == null) {
-                internalState = deviceDumpSupplier.invoke()
-            } else {
-                StateSyncBuilder().withValidState().waitFor()
-            }
-            return internalState ?: error("Unable to fetch an internal state")
-        }
-
-    protected open fun updateCurrState(value: DeviceStateDump) {
-        internalState = value
-    }
-
-    /**
-     * @return a [WindowState] from the current device state matching [componentMatcher], or null
-     * otherwise
-     *
-     * @param componentMatcher Components to search
-     */
-    fun getWindow(componentMatcher: IComponentMatcher): WindowState? {
-        return this.currentState.wmState.windowStates.firstOrNull {
-            componentMatcher.windowMatchesAnyOf(it)
-        }
-    }
-
-    /**
-     * @return The frame [Region] a [WindowState] matching [componentMatcher]
-     *
-     * @param componentMatcher Components to search
-     */
-    fun getWindowRegion(componentMatcher: IComponentMatcher): Region =
-        getWindow(componentMatcher)?.frameRegion ?: Region.EMPTY
-
-    /**
-     * Class to build conditions for waiting on specific [WindowManagerTrace] and [LayersTrace]
-     * conditions
-     */
-    inner class StateSyncBuilder {
-        private val conditionBuilder = createConditionBuilder()
-        private var lastMessage = ""
-
-        private fun createConditionBuilder(): WaitCondition.Builder<DeviceStateDump> =
-            WaitCondition.Builder(deviceDumpSupplier, numRetries)
-                .onStart { Trace.beginSection(it) }
-                .onEnd { Trace.endSection() }
-                .onSuccess { updateCurrState(it) }
-                .onFailure { updateCurrState(it) }
-                .onLog { msg, isError ->
-                    lastMessage = msg
-                    if (isError) {
-                        CrossPlatform.log.e(LOG_TAG, msg)
-                    } else {
-                        CrossPlatform.log.d(LOG_TAG, msg)
-                    }
-                }
-                .onRetry { SystemClock.sleep(retryIntervalMs) }
-
-        /**
-         * Adds a new [condition] to the list
-         *
-         * @param condition to wait for
-         */
-        fun add(condition: Condition<DeviceStateDump>): StateSyncBuilder = apply {
-            conditionBuilder.withCondition(condition)
-        }
-
-        /**
-         * Adds a new [condition] to the list
-         *
-         * @param message describing the condition
-         * @param condition to wait for
-         */
-        @JvmOverloads
-        fun add(message: String = "", condition: (DeviceStateDump) -> Boolean): StateSyncBuilder =
-            add(Condition(message, condition))
-
-        /**
-         * Waits until the list of conditions added to [conditionBuilder] are satisfied
-         *
-         * @return if the device state passed all conditions or not
-         */
-        fun waitFor(): Boolean {
-            val passed = conditionBuilder.build().waitFor()
-            // Ensure WindowManagerService wait until all animations have completed
-            instrumentation.waitForIdleSync()
-            instrumentation.uiAutomation.syncInputTransactions()
-            return passed
-        }
-
-        /**
-         * Waits until the list of conditions added to [conditionBuilder] are satisfied and verifies
-         * the device state passes all conditions
-         *
-         * @throws IllegalArgumentException if the conditions were not met
-         */
-        fun waitForAndVerify() {
-            val success = waitFor()
-            require(success) { lastMessage }
-        }
-
-        /**
-         * Waits for an app matching [componentMatcher] to be visible, in full screen, and for
-         * nothing to be animating
-         *
-         * @param componentMatcher Components to search
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withFullScreenApp(
-            componentMatcher: IComponentMatcher,
-            displayId: Int = Display.DEFAULT_DISPLAY
-        ) =
-            withFullScreenAppCondition(componentMatcher)
-                .withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isLayerVisible(componentMatcher))
-
-        /**
-         * Waits until the home activity is visible and nothing to be animating
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withHomeActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .withNavOrTaskBarVisible()
-                .withStatusBarVisible()
-                .add(WindowManagerConditionsFactory.isHomeActivityVisible())
-                .add(WindowManagerConditionsFactory.isLauncherLayerVisible())
-
-        /**
-         * Waits until the split-screen divider is visible and nothing to be animating
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withSplitDividerVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isLayerVisible(SPLIT_DIVIDER))
-
-        /**
-         * Waits until the home activity is visible and nothing to be animating
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withRecentsActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isRecentsActivityVisible())
-                .add(WindowManagerConditionsFactory.isLayerVisible(LAUNCHER))
-
-        /**
-         * Wait for specific rotation for the display with id [displayId]
-         *
-         * @param rotation expected. Values are [Surface#Rotation]
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withRotation(
-            rotation: PlatformConsts.Rotation,
-            displayId: Int = Display.DEFAULT_DISPLAY
-        ) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.hasRotation(rotation, displayId))
-
-        /**
-         * Waits until a [WindowState] matching [componentMatcher] has a state of [activityState]
-         *
-         * @param componentMatcher Components to search
-         * @param activityState expected activity state
-         */
-        fun withActivityState(componentMatcher: IComponentMatcher, activityState: String) =
-            add(
-                Condition(
-                    "state of ${componentMatcher.toActivityIdentifier()} to be $activityState"
-                ) { it.wmState.hasActivityState(componentMatcher, activityState) }
-            )
-
-        /**
-         * Waits until the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] are
-         * visible (windows and layers)
-         */
-        fun withNavOrTaskBarVisible() = add(WindowManagerConditionsFactory.isNavOrTaskBarVisible())
-
-        /** Waits until the navigation and status bars are visible (windows and layers) */
-        fun withStatusBarVisible() = add(WindowManagerConditionsFactory.isStatusBarVisible())
-
-        /**
-         * Wait until neither an [Activity] nor a [WindowState] matching [componentMatcher] exist on
-         * the display with id [displayId] and for nothing to be animating
-         *
-         * @param componentMatcher Components to search
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withActivityRemoved(
-            componentMatcher: IComponentMatcher,
-            displayId: Int = Display.DEFAULT_DISPLAY
-        ) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.containsActivity(componentMatcher).negate())
-                .add(WindowManagerConditionsFactory.containsWindow(componentMatcher).negate())
-
-        /**
-         * Wait until the splash screen and snapshot starting windows no longer exist, no layers are
-         * animating, and [WindowManagerState] is idle on display [displayId]
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withAppTransitionIdle(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withSplashScreenGone()
-                .withSnapshotGone()
-                .add(WindowManagerConditionsFactory.isAppTransitionIdle(displayId))
-                .add(WindowManagerConditionsFactory.hasLayersAnimating().negate())
-
-        /**
-         * Wait until least one [WindowState] matching [componentMatcher] is not visible on display
-         * with idd [displayId] and nothing is animating
-         *
-         * @param componentMatcher Components to search
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withWindowSurfaceDisappeared(
-            componentMatcher: IComponentMatcher,
-            displayId: Int = Display.DEFAULT_DISPLAY
-        ) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isWindowSurfaceShown(componentMatcher).negate())
-                .add(WindowManagerConditionsFactory.isLayerVisible(componentMatcher).negate())
-                .add(WindowManagerConditionsFactory.isAppTransitionIdle(displayId))
-
-        /**
-         * Wait until least one [WindowState] matching [componentMatcher] is visible on display with
-         * idd [displayId] and nothing is animating
-         *
-         * @param componentMatcher Components to search
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withWindowSurfaceAppeared(
-            componentMatcher: IComponentMatcher,
-            displayId: Int = Display.DEFAULT_DISPLAY
-        ) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isWindowSurfaceShown(componentMatcher))
-                .add(WindowManagerConditionsFactory.isLayerVisible(componentMatcher))
-
-        /**
-         * Waits until the IME window and layer are visible
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withImeShown(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isImeShown(displayId))
-
-        /**
-         * Waits until the [IME] layer is no longer visible.
-         *
-         * Cannot wait for the window as its visibility information is updated at a later state and
-         * is not reliable in the trace
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withImeGone(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.isLayerVisible(IME).negate())
-
-        /**
-         * Waits until a window is in PIP mode. That is:
-         *
-         * - wait until a window is pinned ([WindowManagerState.pinnedWindows])
-         * - no layers animating
-         * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withPipShown(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId).add(WindowManagerConditionsFactory.hasPipWindow())
-
-        /**
-         * Waits until a window is no longer in PIP mode. That is:
-         *
-         * - wait until there are no pinned ([WindowManagerState.pinnedWindows])
-         * - no layers animating
-         * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
-         *
-         * @param displayId of the target display
-         */
-        @JvmOverloads
-        fun withPipGone(displayId: Int = Display.DEFAULT_DISPLAY) =
-            withAppTransitionIdle(displayId)
-                .add(WindowManagerConditionsFactory.hasPipWindow().negate())
-
-        /** Waits until the [SNAPSHOT] is gone */
-        fun withSnapshotGone() =
-            add(WindowManagerConditionsFactory.isLayerVisible(SNAPSHOT).negate())
-
-        /** Waits until the [SPLASH_SCREEN] is gone */
-        fun withSplashScreenGone() =
-            add(WindowManagerConditionsFactory.isLayerVisible(SPLASH_SCREEN).negate())
-
-        /** Waits until the is no top visible app window in the [WindowManagerState] */
-        fun withoutTopVisibleAppWindows() =
-            add("noAppWindowsOnTop") { it.wmState.topVisibleAppWindow == null }
-
-        /** Waits until the keyguard is showing */
-        fun withKeyguardShowing() = add("withKeyguardShowing") { it.wmState.isKeyguardShowing }
-
-        /**
-         * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
-         *
-         * @param waitForActivityState array of activity states to wait for.
-         */
-        internal fun withValidState(vararg waitForActivityState: WaitForValidActivityState) =
-            waitForValidStateCondition(*waitForActivityState)
-
-        private fun waitForValidStateCondition(vararg waitForCondition: WaitForValidActivityState) =
-            apply {
-                add(WindowManagerConditionsFactory.isWMStateComplete())
-                if (waitForCondition.isNotEmpty()) {
-                    add(
-                        Condition("!shouldWaitForActivities") {
-                            !shouldWaitForActivities(it, *waitForCondition)
-                        }
-                    )
-                }
-            }
-
-        fun withFullScreenAppCondition(componentMatcher: IComponentMatcher) =
-            waitForValidStateCondition(
-                WaitForValidActivityState.Builder(componentMatcher)
-                    .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
-                    .setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
-                    .build()
-            )
-    }
-
-    companion object {
-        /** @return true if it should wait for some activities to become visible. */
-        private fun shouldWaitForActivities(
-            state: DeviceStateDump,
-            vararg waitForActivitiesVisible: WaitForValidActivityState
-        ): Boolean {
-            if (waitForActivitiesVisible.isEmpty()) {
-                return false
-            }
-            // If the caller is interested in waiting for some particular activity windows to be
-            // visible before compute the state. Check for the visibility of those activity windows
-            // and for placing them in correct stacks (if requested).
-            var allActivityWindowsVisible = true
-            var tasksInCorrectStacks = true
-            for (activityState in waitForActivitiesVisible) {
-                val matchingWindowStates =
-                    state.wmState.getMatchingVisibleWindowState(
-                        activityState.activityMatcher
-                            ?: error("Activity name missing in $activityState")
-                    )
-                val activityWindowVisible = matchingWindowStates.isNotEmpty()
-
-                if (!activityWindowVisible) {
-                    CrossPlatform.log.i(
-                        LOG_TAG,
-                        "Activity window not visible: ${activityState.windowIdentifier}"
-                    )
-                    allActivityWindowsVisible = false
-                } else if (!state.wmState.isActivityVisible(activityState.activityMatcher)) {
-                    CrossPlatform.log.i(
-                        LOG_TAG,
-                        "Activity not visible: ${activityState.activityMatcher}"
-                    )
-                    allActivityWindowsVisible = false
-                } else {
-                    // Check if window is already the correct state requested by test.
-                    var windowInCorrectState = false
-                    for (ws in matchingWindowStates) {
-                        if (
-                            activityState.stackId != ActivityTaskManager.INVALID_STACK_ID &&
-                                ws.stackId != activityState.stackId
-                        ) {
-                            continue
-                        }
-                        if (!ws.isWindowingModeCompatible(activityState.windowingMode)) {
-                            continue
-                        }
-                        if (
-                            activityState.activityType !=
-                                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED &&
-                                ws.activityType != activityState.activityType
-                        ) {
-                            continue
-                        }
-                        windowInCorrectState = true
-                        break
-                    }
-                    if (!windowInCorrectState) {
-                        CrossPlatform.log.i(LOG_TAG, "Window in incorrect stack: $activityState")
-                        tasksInCorrectStacks = false
-                    }
-                }
-            }
-            return !allActivityWindowsVisible || !tasksInCorrectStacks
-        }
-
-        private fun ConfigurationContainer.isWindowingModeCompatible(
-            requestedWindowingMode: Int
-        ): Boolean {
-            return when (requestedWindowingMode) {
-                WindowConfiguration.WINDOWING_MODE_UNDEFINED -> true
-                else -> windowingMode == requestedWindowingMode
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt b/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt
deleted file mode 100644
index ae82031..0000000
--- a/libraries/flicker/src/com/android/server/wm/traces/parser/windowmanager/WindowManagerTraceParser.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2020 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.traces.parser.windowmanager
-
-import com.android.server.wm.nano.WindowManagerTraceFileProto
-import com.android.server.wm.nano.WindowManagerTraceProto
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-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.AbstractTraceParser
-
-/** Parser for [WindowManagerTrace] objects containing traces */
-open class WindowManagerTraceParser(private val legacyTrace: Boolean = false) :
-    AbstractTraceParser<
-        WindowManagerTraceFileProto, WindowManagerTraceProto, WindowManagerState, WindowManagerTrace
-    >() {
-    private var realToElapsedTimeOffsetNanos = 0L
-
-    override val traceName: String = "WM Trace"
-
-    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerTraceFileProto =
-        WindowManagerTraceFileProto.parseFrom(bytes)
-
-    override fun createTrace(entries: List<WindowManagerState>): WindowManagerTrace =
-        WindowManagerTrace(entries.toTypedArray())
-
-    override fun getEntries(input: WindowManagerTraceFileProto): List<WindowManagerTraceProto> =
-        input.entry.toList()
-
-    override fun getTimestamp(entry: WindowManagerTraceProto): Timestamp {
-        require(legacyTrace || realToElapsedTimeOffsetNanos != 0L)
-        return CrossPlatform.timestamp.from(
-            elapsedNanos = entry.elapsedRealtimeNanos,
-            unixNanos = entry.elapsedRealtimeNanos + realToElapsedTimeOffsetNanos
-        )
-    }
-
-    override fun onBeforeParse(input: WindowManagerTraceFileProto) {
-        realToElapsedTimeOffsetNanos = input.realToElapsedTimeOffsetNanos
-    }
-
-    override fun doParseEntry(entry: WindowManagerTraceProto): WindowManagerState {
-        return WindowManagerStateBuilder()
-            .atPlace(entry.where)
-            .forTimestamp(entry.elapsedRealtimeNanos)
-            .withRealTimeOffset(realToElapsedTimeOffsetNanos)
-            .forProto(entry.windowManagerService)
-            .build()
-    }
-}
diff --git a/libraries/flicker/test/AndroidManifest.xml b/libraries/flicker/test/AndroidManifest.xml
index 27e1dad..21659ef 100644
--- a/libraries/flicker/test/AndroidManifest.xml
+++ b/libraries/flicker/test/AndroidManifest.xml
@@ -4,7 +4,7 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.wm.flicker">
+          package="android.tools">
 
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
     <!-- Read and write traces from external storage -->
@@ -31,8 +31,8 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.wm.flicker"
+                     android:targetPackage="android.tools"
                      android:label="WindowManager Flicker Lib Test">
     </instrumentation>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/libraries/flicker/test/AndroidTest.xml b/libraries/flicker/test/AndroidTest.xml
index 0035d59..26649d2 100644
--- a/libraries/flicker/test/AndroidTest.xml
+++ b/libraries/flicker/test/AndroidTest.xml
@@ -20,7 +20,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
-        <option name="package" value="com.android.server.wm.flicker"/>
+        <option name="package" value="android.tools"/>
         <option name="hidden-api-checks" value="false" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/libraries/flicker/test/src/android/tools/InitRule.kt b/libraries/flicker/test/src/android/tools/InitRule.kt
new file mode 100644
index 0000000..63a89e9
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/InitRule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import android.annotation.SuppressLint
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.common.TimestampFactory
+import android.tools.common.io.RunStatus
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.traces.ANDROID_LOGGER
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.formatRealTimestamp
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** Standard initialization rule for all flicker tests */
+@SuppressLint("VisibleForTests")
+class InitRule : TestRule {
+    override fun apply(base: Statement, description: Description?): Statement {
+        CrossPlatform.setLogger(ANDROID_LOGGER)
+            .setTimestampFactory(TimestampFactory { formatRealTimestamp(it) })
+        DataStore.clear()
+        Cache.clear()
+        RunStatus.ALL.forEach { outputFileName(it).deleteIfExists() }
+        return base
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/TestComponents.kt b/libraries/flicker/test/src/android/tools/TestComponents.kt
new file mode 100644
index 0000000..f5d1d43
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/TestComponents.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("CommonConstants")
+
+package android.tools
+
+import android.tools.common.datatypes.component.ComponentNameMatcher
+
+internal object TestComponents {
+    @JvmStatic
+    val CHROME = ComponentNameMatcher("com.android.chrome", "com.google.android.apps.chrome.Main")
+
+    @JvmStatic
+    val CHROME_FIRST_RUN =
+        ComponentNameMatcher(
+            "com.android.chrome",
+            "org.chromium.chrome.browser.firstrun.FirstRunActivity"
+        )
+
+    @JvmStatic
+    val CHROME_SPLASH_SCREEN = ComponentNameMatcher("", "Splash Screen com.android.chrome")
+
+    @JvmStatic val DOCKER_STACK_DIVIDER = ComponentNameMatcher("", "DockedStackDivider")
+
+    @JvmStatic val IMAGINARY = ComponentNameMatcher("", "ImaginaryWindow")
+
+    @JvmStatic
+    val IME_ACTIVITY =
+        ComponentNameMatcher(
+            "com.android.server.wm.flicker.testapp",
+            "com.android.server.wm.flicker.testapp.ImeActivity"
+        )
+
+    @JvmStatic
+    val LAUNCHER =
+        ComponentNameMatcher(
+            "com.google.android.apps.nexuslauncher",
+            "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+        )
+
+    @JvmStatic val PIP_OVERLAY = ComponentNameMatcher("", "pip-dismiss-overlay")
+
+    @JvmStatic
+    val SIMPLE_APP =
+        ComponentNameMatcher(
+            "com.android.server.wm.flicker.testapp",
+            "com.android.server.wm.flicker.testapp.SimpleActivity"
+        )
+
+    @JvmStatic
+    val NON_RESIZEABLE_APP =
+        ComponentNameMatcher(
+            "com.android.server.wm.flicker.testapp",
+            "com.android.server.wm.flicker.testapp.NonResizeableActivity"
+        )
+
+    private const val SHELL_PKG_NAME = "com.android.wm.shell.flicker.testapp"
+
+    @JvmStatic
+    val SHELL_SPLIT_SCREEN_PRIMARY =
+        ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.SplitScreenActivity")
+
+    @JvmStatic
+    val SHELL_SPLIT_SCREEN_SECONDARY =
+        ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.SplitScreenSecondaryActivity")
+
+    @JvmStatic val FIXED_APP = ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.FixedActivity")
+
+    @JvmStatic val PIP_APP = ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.PipActivity")
+
+    @JvmStatic val SCREEN_DECOR_OVERLAY = ComponentNameMatcher("", "ScreenDecorOverlay")
+
+    @JvmStatic
+    val WALLPAPER =
+        ComponentNameMatcher(
+            "",
+            "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+        )
+}
diff --git a/libraries/flicker/test/src/android/tools/TestTraces.kt b/libraries/flicker/test/src/android/tools/TestTraces.kt
new file mode 100644
index 0000000..0ba191a
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/TestTraces.kt
@@ -0,0 +1,116 @@
+/*
+ * 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
+
+import android.tools.common.CrossPlatform
+import android.tools.device.traces.TraceConfig
+import android.tools.device.traces.TraceConfigs
+
+object TestTraces {
+    object LayerTrace {
+        private const val ASSET = "layers_trace.winscope"
+        val START_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1618663562444)
+        val SLICE_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1618715108595)
+        val END_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1620770824112)
+        val FILE
+            get() = readAssetAsFile(ASSET)
+    }
+
+    object WMTrace {
+        private const val ASSET = "wm_trace.winscope"
+        val START_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1618650751245)
+        val SLICE_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1618730362295)
+        val END_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1620756218174)
+        val FILE
+            get() = readAssetAsFile(ASSET)
+    }
+
+    object EventLog {
+        private const val ASSET = "eventlog.winscope"
+        val START_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594369069951546)
+        val SLICE_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594384516466159)
+        val END_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594389958451901)
+        val FILE
+            get() = readAssetAsFile(ASSET)
+    }
+
+    object TransactionTrace {
+        private const val ASSET = "transactions_trace.winscope"
+        val START_TIME =
+            CrossPlatform.timestamp.from(
+                systemUptimeNanos = 1556111744859,
+                elapsedNanos = 1556111744859
+            )
+        val VALID_SLICE_TIME =
+            CrossPlatform.timestamp.from(
+                systemUptimeNanos = 1556147625539,
+                elapsedNanos = 1556147625539
+            )
+        val INVALID_SLICE_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1622127714039 + 1)
+        val END_TIME =
+            CrossPlatform.timestamp.from(
+                systemUptimeNanos = 1622127714039,
+                elapsedNanos = 1622127714039
+            )
+        val FILE
+            get() = readAssetAsFile(ASSET)
+    }
+
+    object TransitionTrace {
+        private const val ASSET = "transition_trace.winscope"
+        val START_TIME =
+            CrossPlatform.timestamp.from(
+                elapsedNanos = 169632392038504,
+                systemUptimeNanos = 0,
+                unixNanos = 0
+            )
+        val VALID_SLICE_TIME =
+            CrossPlatform.timestamp.from(
+                elapsedNanos = 169632392038504,
+                systemUptimeNanos = 0,
+                unixNanos = 0
+            )
+        val INVALID_SLICE_TIME =
+            CrossPlatform.timestamp.from(
+                elapsedNanos = 0L,
+                systemUptimeNanos = TransactionTrace.INVALID_SLICE_TIME.systemUptimeNanos
+            )
+        val END_TIME =
+            CrossPlatform.timestamp.from(
+                elapsedNanos = 169632392038504,
+                systemUptimeNanos = 0,
+                unixNanos = 0
+            )
+        val FILE
+            get() = readAssetAsFile(ASSET)
+    }
+
+    val TIME_5 = CrossPlatform.timestamp.from(5, 5, 5)
+    val TIME_10 = CrossPlatform.timestamp.from(10, 10, 10)
+
+    val TEST_TRACE_CONFIG =
+        TraceConfigs(
+            wmTrace =
+                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
+            layersTrace =
+                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
+            transitionsTrace =
+                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
+            transactionsTrace =
+                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false)
+        )
+}
diff --git a/libraries/flicker/test/src/android/tools/Utils.kt b/libraries/flicker/test/src/android/tools/Utils.kt
new file mode 100644
index 0000000..4754437
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/Utils.kt
@@ -0,0 +1,348 @@
+/*
+ * 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
+
+import android.app.Instrumentation
+import android.content.Context
+import android.tools.common.CrossPlatform
+import android.tools.common.IScenario
+import android.tools.common.ScenarioBuilder
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.common.io.RunStatus
+import android.tools.common.io.WINSCOPE_EXT
+import android.tools.common.parsers.events.EventLogParser
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.device.flicker.datastore.CachedResultWriter
+import android.tools.device.flicker.legacy.AbstractFlickerTestData
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.monitors.ScreenRecorder
+import android.tools.device.traces.monitors.events.EventLogMonitor
+import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
+import android.tools.device.traces.monitors.surfaceflinger.TransactionsTraceMonitor
+import android.tools.device.traces.monitors.wm.TransitionsTraceMonitor
+import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
+import android.tools.device.traces.parsers.surfaceflinger.TransactionsTraceParser
+import android.tools.device.traces.parsers.wm.TransitionsTraceParser
+import android.tools.device.traces.parsers.wm.WindowManagerDumpParser
+import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.common.io.ByteStreams
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth
+import java.io.File
+import java.io.FileInputStream
+import java.io.IOException
+import java.util.zip.ZipInputStream
+import org.mockito.Mockito
+
+internal val TEST_SCENARIO = ScenarioBuilder().forClass("test").build()
+
+internal fun outputFileName(status: RunStatus) =
+    File("/sdcard/flicker/${status.prefix}_test_ROTATION_0_GESTURAL_NAV.zip")
+
+internal fun newTestResultWriter() =
+    ResultWriter()
+        .forScenario(TEST_SCENARIO)
+        .withOutputDir(getDefaultFlickerOutputDir())
+        .setRunComplete()
+
+internal fun newTestCachedResultWriter() =
+    CachedResultWriter()
+        .forScenario(TEST_SCENARIO)
+        .withOutputDir(getDefaultFlickerOutputDir())
+        .setRunComplete()
+
+internal fun readWmTraceFromFile(
+    relativePath: String,
+    from: Long = Long.MIN_VALUE,
+    to: Long = Long.MAX_VALUE,
+    addInitialEntry: Boolean = true,
+    legacyTrace: Boolean = false,
+): WindowManagerTrace {
+    return try {
+        WindowManagerTraceParser(legacyTrace)
+            .parse(
+                readAsset(relativePath),
+                CrossPlatform.timestamp.from(elapsedNanos = from),
+                CrossPlatform.timestamp.from(elapsedNanos = to),
+                addInitialEntry,
+                clearCache = false
+            )
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+internal fun readWmTraceFromDumpFile(relativePath: String): WindowManagerTrace {
+    return try {
+        WindowManagerDumpParser().parse(readAsset(relativePath), clearCache = false)
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+internal fun readLayerTraceFromFile(
+    relativePath: String,
+    ignoreOrphanLayers: Boolean = true,
+    legacyTrace: Boolean = false,
+): LayersTrace {
+    return try {
+        LayersTraceParser(
+                ignoreLayersStackMatchNoDisplay = false,
+                ignoreLayersInVirtualDisplay = false,
+                legacyTrace = legacyTrace,
+            ) {
+                ignoreOrphanLayers
+            }
+            .parse(readAsset(relativePath))
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+internal fun readTransactionsTraceFromFile(relativePath: String): TransactionsTrace {
+    return try {
+        TransactionsTraceParser().parse(readAsset(relativePath))
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+internal fun readTransitionsTraceFromFile(
+    relativePath: String,
+    transactionsTrace: TransactionsTrace
+): TransitionsTrace {
+    return try {
+        TransitionsTraceParser().parse(readAsset(relativePath))
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+internal fun readEventLogFromFile(relativePath: String): EventLog {
+    return try {
+        EventLogParser().parse(readAsset(relativePath))
+    } catch (e: Exception) {
+        throw RuntimeException(e)
+    }
+}
+
+@Throws(Exception::class)
+internal fun readAsset(relativePath: String): ByteArray {
+    val context: Context = InstrumentationRegistry.getInstrumentation().context
+    val inputStream = context.resources.assets.open("testdata/$relativePath")
+    return ByteStreams.toByteArray(inputStream)
+}
+
+@Throws(IOException::class)
+fun readAssetAsFile(relativePath: String): File {
+    val context: Context = InstrumentationRegistry.getInstrumentation().context
+    return File(context.cacheDir, relativePath).also {
+        if (!it.exists()) {
+            it.outputStream().use { cache ->
+                context.assets.open("testdata/$relativePath").use { inputStream ->
+                    inputStream.copyTo(cache)
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Runs `r` and asserts that an exception with type `expectedThrowable` is thrown.
+ * @param r the [Runnable] which is run and expected to throw.
+ * @throws AssertionError if `r` does not throw, or throws a runnable that is not an instance of
+ * `expectedThrowable`.
+ */
+inline fun <reified ExceptionType> assertThrows(r: () -> Unit): ExceptionType {
+    try {
+        r()
+    } catch (t: Throwable) {
+        when {
+            ExceptionType::class.java.isInstance(t) -> return t as ExceptionType
+            t is Exception ->
+                throw AssertionError(
+                    "Expected ${ExceptionType::class.java}, but got '${t.javaClass}'",
+                    t
+                )
+            // Re-throw Errors and other non-Exception throwables.
+            else -> throw t
+        }
+    }
+    error("Expected exception ${ExceptionType::class.java}, but nothing was thrown")
+}
+
+fun assertFailureFact(
+    failure: FlickerSubjectException,
+    factKey: String,
+    factIndex: Int = 0
+): StringSubject {
+    val matchingFacts = failure.facts.filter { it.key == factKey }
+
+    if (factIndex >= matchingFacts.size) {
+        val message = buildString {
+            appendLine("Cannot find failure fact with key '$factKey' and index $factIndex")
+            appendLine()
+            appendLine("Available facts:")
+            failure.facts.forEach { appendLine(it.toString()) }
+        }
+        throw AssertionError(message)
+    }
+
+    return Truth.assertThat(matchingFacts[factIndex].value)
+}
+
+fun assertThatErrorContainsDebugInfo(error: Throwable, withBlameEntry: Boolean = true) {
+    Truth.assertThat(error).hasMessageThat().contains("What?")
+    Truth.assertThat(error).hasMessageThat().contains("Where?")
+    Truth.assertThat(error).hasMessageThat().contains("Facts")
+    Truth.assertThat(error).hasMessageThat().contains("Trace start")
+    Truth.assertThat(error).hasMessageThat().contains("Trace end")
+
+    if (withBlameEntry) {
+        Truth.assertThat(error).hasMessageThat().contains("State")
+    }
+}
+
+fun assertArchiveContainsFiles(archivePath: File, expectedFiles: List<String>) {
+    Truth.assertWithMessage("Expected trace archive `$archivePath` to exist")
+        .that(archivePath.exists())
+        .isTrue()
+
+    val archiveStream = ZipInputStream(FileInputStream(archivePath))
+
+    val actualFiles = generateSequence { archiveStream.nextEntry }.map { it.name }.toList()
+
+    Truth.assertWithMessage("Trace archive doesn't contain all expected traces")
+        .that(actualFiles)
+        .containsExactlyElementsIn(expectedFiles)
+}
+
+fun getScenarioTraces(scenario: String): FlickerBuilder.TraceFiles {
+    val randomString = (1..10).map { (('A'..'Z') + ('a'..'z')).random() }.joinToString("")
+
+    lateinit var wmTrace: File
+    lateinit var layersTrace: File
+    lateinit var transactionsTrace: File
+    lateinit var transitionsTrace: File
+    lateinit var eventLog: File
+    val traces =
+        mapOf<String, (File) -> Unit>(
+            "wm_trace" to { wmTrace = it },
+            "layers_trace" to { layersTrace = it },
+            "transactions_trace" to { transactionsTrace = it },
+            "transition_trace" to { transitionsTrace = it },
+            "eventlog" to { eventLog = it }
+        )
+    for ((traceName, resultSetter) in traces.entries) {
+        val traceBytes = readAsset("scenarios/$scenario/$traceName$WINSCOPE_EXT")
+        val traceFile =
+            getDefaultFlickerOutputDir().resolve("${traceName}_$randomString$WINSCOPE_EXT")
+        traceFile.parentFile.mkdirs()
+        traceFile.createNewFile()
+        traceFile.writeBytes(traceBytes)
+        resultSetter.invoke(traceFile)
+    }
+
+    return FlickerBuilder.TraceFiles(
+        wmTrace,
+        layersTrace,
+        transactionsTrace,
+        transitionsTrace,
+        eventLog
+    )
+}
+
+fun assertExceptionMessage(error: Throwable?, expectedValue: String) {
+    Truth.assertWithMessage("Expected exception")
+        .that(error)
+        .hasMessageThat()
+        .contains(expectedValue)
+}
+
+fun assertExceptionMessageCause(error: Throwable?, expectedValue: String) {
+    Truth.assertWithMessage("Expected cause")
+        .that(error)
+        .hasCauseThat()
+        .hasMessageThat()
+        .contains(expectedValue)
+}
+
+fun createMockedFlicker(
+    setup: List<IFlickerTestData.() -> Unit> = emptyList(),
+    teardown: List<IFlickerTestData.() -> Unit> = emptyList(),
+    transitions: List<IFlickerTestData.() -> Unit> = emptyList(),
+    extraMonitor: ITransitionMonitor? = null
+): IFlickerTestData {
+    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+    val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
+    val monitors: MutableList<ITransitionMonitor> =
+        mutableListOf(WindowManagerTraceMonitor(), LayersTraceMonitor())
+    extraMonitor?.let { monitors.add(it) }
+    Mockito.`when`(mockedFlicker.wmHelper).thenReturn(WindowManagerStateHelper())
+    Mockito.`when`(mockedFlicker.device).thenReturn(uiDevice)
+    Mockito.`when`(mockedFlicker.outputDir).thenReturn(getDefaultFlickerOutputDir())
+    Mockito.`when`(mockedFlicker.traceMonitors).thenReturn(monitors)
+    Mockito.`when`(mockedFlicker.transitionSetup).thenReturn(setup)
+    Mockito.`when`(mockedFlicker.transitionTeardown).thenReturn(teardown)
+    Mockito.`when`(mockedFlicker.transitions).thenReturn(transitions)
+    return mockedFlicker
+}
+
+fun captureTrace(scenario: IScenario, actions: () -> Unit): ResultReader {
+    if (scenario.isEmpty) {
+        ScenarioBuilder().forClass("UNNAMED_CAPTURE").build()
+    }
+    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    val writer =
+        ResultWriter()
+            .forScenario(scenario)
+            .withOutputDir(getDefaultFlickerOutputDir())
+            .setRunComplete()
+    val monitors =
+        listOf(
+            ScreenRecorder(instrumentation.targetContext),
+            EventLogMonitor(),
+            TransactionsTraceMonitor(),
+            TransitionsTraceMonitor(),
+            WindowManagerTraceMonitor(),
+            LayersTraceMonitor()
+        )
+    try {
+        monitors.forEach { it.start() }
+        actions.invoke()
+    } finally {
+        monitors.forEach { it.stop(writer) }
+    }
+    val result = writer.write()
+
+    return ResultReader(result, DEFAULT_TRACE_CONFIG)
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/AssertionsCheckerTest.kt b/libraries/flicker/test/src/android/tools/common/assertions/AssertionsCheckerTest.kt
new file mode 100644
index 0000000..027f51e
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/AssertionsCheckerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.assertions
+
+import android.tools.InitRule
+import android.tools.assertFailureFact
+import android.tools.assertThrows
+import android.tools.common.CrossPlatform
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+import android.tools.common.flicker.assertions.AssertionsChecker
+import android.tools.common.flicker.assertions.Fact
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerSubjectException
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [AssertionsChecker] tests. To run this test: `atest
+ * FlickerLibTest:AssertionsCheckerTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AssertionsCheckerTest {
+    @Test
+    fun emptyRangePasses() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.test(emptyList())
+    }
+
+    @Test
+    fun canCheckChangingAssertions() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsIgnoreOptionalStart() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsIgnoreOptionalEnd() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsIgnoreOptionalMiddle() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData0") { it.isData0() }
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsIgnoreOptionalMultiple() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData0") { it.isData0() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.add("isData1", isOptional = true) { it.isData1() }
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsWithNoAssertions() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.test(getTestEntries(42, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun canCheckChangingAssertionsWithSingleAssertion() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.test(getTestEntries(42, 42, 42, 42, 42))
+    }
+
+    @Test
+    fun canFailCheckChangingAssertionsIfStartingAssertionFails() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        val failure =
+            assertThrows<FlickerSubjectException> { checker.test(getTestEntries(0, 0, 0, 0, 0)) }
+        assertFailureFact(failure, "Assertion failed").isEqualTo("data is 42")
+    }
+
+    @Test
+    fun canCheckChangingAssertionsSkipUntilFirstSuccess() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.skipUntilFirstAssertion()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        checker.test(getTestEntries(0, 42, 0, 0, 0))
+    }
+
+    @Test
+    fun canFailCheckChangingAssertionsIfStartingAssertionAlwaysPasses() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42") { it.isData42() }
+        checker.add("isData0") { it.isData0() }
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                checker.test(getTestEntries(42, 42, 42, 42, 42))
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("Assertion never failed: isData42")
+    }
+
+    @Test
+    fun canFailCheckChangingAssertionsIfUsingCompoundAssertion() {
+        val checker = AssertionsChecker<SimpleEntrySubject>()
+        checker.add("isData42/0") { it.isData42().isData0() }
+        val failure =
+            assertThrows<FlickerSubjectException> { checker.test(getTestEntries(0, 0, 0, 0, 0)) }
+        assertFailureFact(failure, "Assertion failed").isEqualTo("data is 42")
+    }
+
+    private class SimpleEntrySubject(private val entry: SimpleEntry) : FlickerSubject() {
+        override val timestamp = CrossPlatform.timestamp.empty()
+        override val parent = null
+        override val selfFacts = listOf(Fact("SimpleEntry", entry.mData.toString()))
+
+        fun isData42() = apply { check { "data is 42" }.that(entry.mData).isEqual(42) }
+
+        fun isData0() = apply { check { "data is 0" }.that(entry.mData).isEqual(0) }
+
+        fun isData1() = apply { check { "data is 1" }.that(entry.mData).isEqual(1) }
+
+        companion object {
+            /** User-defined entry point */
+            @JvmStatic
+            fun assertThat(entry: SimpleEntry): SimpleEntrySubject {
+                return SimpleEntrySubject(entry)
+            }
+        }
+    }
+
+    data class SimpleEntry(override val timestamp: Timestamp, val mData: Int) : ITraceEntry
+
+    companion object {
+        /**
+         * Returns a list of SimpleEntry objects with `data` and incremental timestamps starting at
+         * 0.
+         */
+        private fun getTestEntries(vararg data: Int): List<SimpleEntrySubject> =
+            data.indices.map {
+                SimpleEntrySubject(
+                    SimpleEntry(CrossPlatform.timestamp.from(elapsedNanos = it.toLong()), data[it])
+                )
+            }
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/BaseSubjectsParserTestParse.kt b/libraries/flicker/test/src/android/tools/common/assertions/BaseSubjectsParserTestParse.kt
new file mode 100644
index 0000000..e445e7a
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/BaseSubjectsParserTestParse.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.assertions
+
+import android.tools.InitRule
+import android.tools.common.Tag
+import android.tools.common.Timestamp
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.io.ResultWriter
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+abstract class BaseSubjectsParserTestParse {
+    protected abstract val assetFile: File
+    protected abstract val subjectName: String
+    protected abstract val expectedStartTime: Timestamp
+    protected abstract val expectedEndTime: Timestamp
+    protected abstract val traceType: TraceType
+
+    protected abstract fun getTime(timestamp: Timestamp): Long
+
+    protected abstract fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>?
+
+    protected abstract fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject?
+
+    protected open fun writeTrace(writer: ResultWriter): ResultWriter {
+        writer.addTraceResult(traceType, assetFile)
+        return writer
+    }
+
+    @Before
+    fun setup() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+    }
+
+    @Test
+    fun parseTraceSubject() {
+        val writer = writeTrace(newTestResultWriter())
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val parser = TestSubjectsParser(reader)
+        val subject = doParseTrace(parser) ?: error("$subjectName not built")
+
+        Truth.assertWithMessage(subjectName).that(subject.subjects).isNotEmpty()
+        Truth.assertWithMessage("$subjectName start")
+            .that(getTime(subject.subjects.first().timestamp))
+            .isEqualTo(getTime(expectedStartTime))
+        Truth.assertWithMessage("$subjectName end")
+            .that(getTime(subject.subjects.last().timestamp))
+            .isEqualTo(getTime(expectedEndTime))
+    }
+
+    @Test
+    fun parseStateSubjectTagStart() {
+        doParseStateSubjectAndValidate(Tag.START, expectedStartTime)
+    }
+
+    @Test
+    fun parseStateSubjectTagEnd() {
+        doParseStateSubjectAndValidate(Tag.END, expectedEndTime)
+    }
+
+    @Test
+    fun readTraceNullWhenDoesNotExist() {
+        val writer = newTestResultWriter()
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val parser = TestSubjectsParser(reader)
+        val subject = doParseTrace(parser)
+
+        Truth.assertWithMessage(subjectName).that(subject).isNull()
+    }
+
+    private fun doParseStateSubjectAndValidate(tag: String, expectedTime: Timestamp) {
+        val writer = writeTrace(newTestResultWriter())
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val parser = TestSubjectsParser(reader)
+        val subject = doParseState(parser, tag) ?: error("$subjectName tag=$tag not built")
+
+        Truth.assertWithMessage("$subjectName - $tag")
+            .that(getTime(subject.timestamp))
+            .isEqualTo(getTime(expectedTime))
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/Consts.kt b/libraries/flicker/test/src/android/tools/common/assertions/Consts.kt
new file mode 100644
index 0000000..bba2480
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/Consts.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.assertions
+
+object Consts {
+    internal const val FAILURE = "Expected failure"
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTest.kt b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTest.kt
new file mode 100644
index 0000000..963e30c
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.assertions
+
+import android.tools.InitRule
+import android.tools.assertThrows
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.ResultReader
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import java.io.FileNotFoundException
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [SubjectsParser] */
+class SubjectsParserTest {
+
+    @Test
+    fun failFileNotFound() {
+        val data = newTestResultWriter().write()
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        val parser = SubjectsParser(ResultReader(data, DEFAULT_TRACE_CONFIG))
+        assertThrows<FileNotFoundException> { parser.getSubjects(Tag.ALL) }
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseLayers.kt b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseLayers.kt
new file mode 100644
index 0000000..d5a2eb7
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseLayers.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.assertions
+
+import android.annotation.SuppressLint
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import android.tools.common.io.TraceType
+
+@SuppressLint("VisibleForTests")
+class SubjectsParserTestParseLayers : BaseSubjectsParserTestParse() {
+    override val assetFile = TestTraces.LayerTrace.FILE
+    override val expectedStartTime = TestTraces.LayerTrace.START_TIME
+    override val expectedEndTime = TestTraces.LayerTrace.END_TIME
+    override val subjectName = "SF Trace"
+    override val traceType = TraceType.SF
+
+    override fun getTime(timestamp: Timestamp) = timestamp.systemUptimeNanos
+
+    override fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>? =
+        parser.doGetLayersTraceSubject()
+
+    override fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject? =
+        parser.doGetLayerTraceEntrySubject(tag)
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseWM.kt b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseWM.kt
new file mode 100644
index 0000000..e1ed91f
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/SubjectsParserTestParseWM.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.assertions
+
+import android.annotation.SuppressLint
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerTraceSubject
+import android.tools.common.io.TraceType
+
+@SuppressLint("VisibleForTests")
+class SubjectsParserTestParseWM : BaseSubjectsParserTestParse() {
+    override val assetFile = TestTraces.WMTrace.FILE
+    override val expectedStartTime = TestTraces.WMTrace.START_TIME
+    override val expectedEndTime = TestTraces.WMTrace.END_TIME
+    override val subjectName = "WM Trace"
+    override val traceType = TraceType.WM
+
+    override fun getTime(timestamp: Timestamp) = timestamp.elapsedNanos
+
+    override fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>? =
+        parser.doGetWmTraceSubject()
+
+    override fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject? =
+        parser.doGetWmStateSubject(tag)
+}
diff --git a/libraries/flicker/test/src/android/tools/common/assertions/TestSubjectsParser.kt b/libraries/flicker/test/src/android/tools/common/assertions/TestSubjectsParser.kt
new file mode 100644
index 0000000..89fee75
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/assertions/TestSubjectsParser.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.assertions
+
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+import android.tools.device.traces.io.ResultReader
+
+/** Wrapper for [SubjectsParser] with looser visibility */
+class TestSubjectsParser(resultReader: ResultReader) : SubjectsParser(resultReader) {
+    public override fun doGetEventLogSubject(): EventLogSubject? = super.doGetEventLogSubject()
+
+    public override fun doGetWmTraceSubject(): WindowManagerTraceSubject? =
+        super.doGetWmTraceSubject()
+
+    public override fun doGetLayersTraceSubject(): LayersTraceSubject? =
+        super.doGetLayersTraceSubject()
+
+    public override fun doGetLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? =
+        super.doGetLayerTraceEntrySubject(tag)
+
+    public override fun doGetWmStateSubject(tag: String): WindowManagerStateSubject? =
+        super.doGetWmStateSubject(tag)
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceResultsCollectorTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceResultsCollectorTest.kt
new file mode 100644
index 0000000..67b02a3
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceResultsCollectorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * 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
+
+import android.device.collectors.DataRecord
+import android.tools.InitRule
+import android.tools.common.flicker.assertors.AssertionResult
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.flicker.assertors.IFaasAssertion
+import android.tools.common.io.IReader
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.device.flicker.FlickerServiceResultsCollector
+import android.tools.device.traces.io.ParsedTracesReader
+import android.tools.utils.KotlinMockito
+import android.tools.utils.MockLayersTraceBuilder
+import android.tools.utils.MockWindowManagerTraceBuilder
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+import org.junit.runners.MethodSorters
+import org.mockito.Mockito
+
+/**
+ * Contains [FlickerServiceResultsCollector] tests. To run this test: `atest
+ * FlickerLibTest:FlickerServiceResultsCollectorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FlickerServiceResultsCollectorTest {
+    @Test
+    fun reportsMetricsOnlyForPassingTestsIfRequested() {
+        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
+        Mockito.`when`(mockTraceCollector.getResultReader())
+            .thenReturn(
+                ParsedTracesReader(
+                    wmTrace = MockWindowManagerTraceBuilder().build(),
+                    layersTrace = MockLayersTraceBuilder().build(),
+                    transitionsTrace = TransitionsTrace(emptyArray()),
+                    transactionsTrace = null
+                )
+            )
+        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
+        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
+            .thenReturn(listOf(mockSuccessfulAssertionResult))
+
+        val collector =
+            FlickerServiceResultsCollector(
+                tracesCollector = mockTraceCollector,
+                flickerService = mockFlickerService,
+                reportOnlyForPassingTests = true,
+            )
+
+        val runData = DataRecord()
+        val runDescription = Description.createSuiteDescription("TestSuite")
+        val testData = DataRecord()
+        val testDescription = Description.createTestDescription("TestClass", "TestName")
+
+        collector.onTestRunStart(runData, runDescription)
+        collector.onTestStart(testData, testDescription)
+        collector.onTestFail(testData, testDescription, Mockito.mock(Failure::class.java))
+        collector.onTestEnd(testData, testDescription)
+        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
+
+        Truth.assertThat(collector.executionErrors).isEmpty()
+        Truth.assertThat(collector.assertionResultsByTest[testDescription]).isNull()
+        Truth.assertThat(runData.hasMetrics()).isFalse()
+    }
+
+    @Test
+    fun reportsMetricsForFailingTestsIfRequested() {
+        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
+        Mockito.`when`(mockTraceCollector.getResultReader())
+            .thenReturn(
+                ParsedTracesReader(
+                    wmTrace = MockWindowManagerTraceBuilder().build(),
+                    layersTrace = MockLayersTraceBuilder().build(),
+                    transitionsTrace = TransitionsTrace(emptyArray()),
+                    transactionsTrace = null
+                )
+            )
+        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
+        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
+            .thenReturn(listOf(mockSuccessfulAssertionResult))
+        val collector =
+            FlickerServiceResultsCollector(
+                tracesCollector = mockTraceCollector,
+                flickerService = mockFlickerService,
+                reportOnlyForPassingTests = false,
+            )
+
+        val runData = DataRecord()
+        val runDescription = Description.createSuiteDescription("TestSuite")
+        val testData = DataRecord()
+        val testDescription = Description.createTestDescription("TestClass", "TestName")
+
+        collector.onTestRunStart(runData, runDescription)
+        collector.onTestStart(testData, testDescription)
+        collector.onTestFail(testData, testDescription, Mockito.mock(Failure::class.java))
+        collector.onTestEnd(testData, testDescription)
+        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
+
+        Truth.assertThat(collector.executionErrors).isEmpty()
+        Truth.assertThat(collector.resultsForTest(testDescription)).isNotEmpty()
+        Truth.assertThat(testData.hasMetrics()).isTrue()
+    }
+
+    @Test
+    fun collectsMetricsForEachTestIfRequested() {
+        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
+        Mockito.`when`(mockTraceCollector.getResultReader())
+            .thenReturn(
+                ParsedTracesReader(
+                    wmTrace = MockWindowManagerTraceBuilder().build(),
+                    layersTrace = MockLayersTraceBuilder().build(),
+                    transitionsTrace = TransitionsTrace(emptyArray()),
+                    transactionsTrace = null
+                )
+            )
+        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
+        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
+            .thenReturn(listOf(mockSuccessfulAssertionResult))
+        val collector =
+            FlickerServiceResultsCollector(
+                tracesCollector = mockTraceCollector,
+                flickerService = mockFlickerService,
+                collectMetricsPerTest = true,
+            )
+
+        val runData = DataRecord()
+        val runDescription = Description.createSuiteDescription("TestSuite")
+        val testData = DataRecord()
+        val testDescription = Description.createTestDescription("TestClass", "TestName")
+
+        collector.onTestRunStart(runData, runDescription)
+        collector.onTestStart(testData, testDescription)
+        collector.onTestEnd(testData, testDescription)
+        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
+
+        Truth.assertThat(collector.executionErrors).isEmpty()
+        Truth.assertThat(collector.resultsForTest(testDescription)).isNotEmpty()
+        Truth.assertThat(testData.hasMetrics()).isTrue()
+    }
+
+    @Test
+    fun collectsMetricsForEntireTestRunIfRequested() {
+        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
+        Mockito.`when`(mockTraceCollector.getResultReader())
+            .thenReturn(
+                ParsedTracesReader(
+                    wmTrace = MockWindowManagerTraceBuilder().build(),
+                    layersTrace = MockLayersTraceBuilder().build(),
+                    transitionsTrace = TransitionsTrace(emptyArray()),
+                    transactionsTrace = null
+                )
+            )
+        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
+        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
+            .thenReturn(listOf(mockSuccessfulAssertionResult))
+        val collector =
+            FlickerServiceResultsCollector(
+                tracesCollector = mockTraceCollector,
+                flickerService = mockFlickerService,
+                collectMetricsPerTest = false,
+            )
+
+        val runData = DataRecord()
+        val runDescription = Description.createSuiteDescription("TestSuite")
+        val testData = DataRecord()
+        val testDescription = Description.createTestDescription("TestClass", "TestName")
+
+        collector.onTestRunStart(runData, runDescription)
+        collector.onTestStart(testData, testDescription)
+        collector.onTestEnd(testData, testDescription)
+        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
+
+        Truth.assertThat(collector.executionErrors).isEmpty()
+        Truth.assertThat(collector.assertionResults).isNotEmpty()
+        Truth.assertThat(runData.hasMetrics()).isTrue()
+    }
+
+    companion object {
+        val mockSuccessfulAssertionResult =
+            AssertionResult(
+                object : IFaasAssertion {
+                    override val name: String
+                        get() = "MockAssertion"
+                    override val stabilityGroup: AssertionInvocationGroup
+                        get() = AssertionInvocationGroup.BLOCKING
+                    override fun evaluate(): IAssertionResult {
+                        error("Unimplemented - shouldn't be called")
+                    }
+                },
+                assertionError = null
+            )
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceRuleTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceRuleTest.kt
new file mode 100644
index 0000000..c3c1a34
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceRuleTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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
+
+import android.tools.InitRule
+import android.tools.common.flicker.assertors.AssertionResult
+import android.tools.common.flicker.assertors.IAssertionResult
+import android.tools.common.flicker.assertors.IFaasAssertion
+import android.tools.device.flicker.IFlickerServiceResultsCollector
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.legacy.runner.Consts
+import android.tools.device.flicker.legacy.runner.ExecutionError
+import android.tools.device.flicker.rules.FlickerServiceRule
+import android.tools.utils.KotlinMockito
+import com.google.common.truth.Truth
+import org.junit.Assume
+import org.junit.AssumptionViolatedException
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runners.MethodSorters
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+
+/**
+ * Contains [FlickerServiceRule] tests. To run this test: `atest
+ * FlickerLibTest:FlickerServiceRuleTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FlickerServiceRuleTest {
+    @Before
+    fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+
+    @Test
+    fun startsTraceCollectionOnTestStarting() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+
+        testRule.starting(mockDescription)
+        Mockito.verify(mockFlickerServiceResultsCollector).testStarted(mockDescription)
+    }
+
+    @Test
+    fun stopsTraceCollectionOnTestFinished() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+
+        testRule.finished(mockDescription)
+        Mockito.verify(mockFlickerServiceResultsCollector).testFinished(mockDescription)
+    }
+
+    @Test
+    fun reportsFailuresToMetricsCollector() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+        val mockError = Throwable("Mock error")
+
+        testRule.failed(mockError, mockDescription)
+        Mockito.verify(mockFlickerServiceResultsCollector)
+            .testFailure(
+                KotlinMockito.argThat {
+                    this.description == mockDescription && this.exception == mockError
+                }
+            )
+    }
+
+    @Test
+    fun reportsSkippedToMetricsCollector() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+        val mockAssumptionFailure = AssumptionViolatedException("Mock error")
+
+        testRule.skipped(mockAssumptionFailure, mockDescription)
+        Mockito.verify(mockFlickerServiceResultsCollector).testSkipped(mockDescription)
+    }
+
+    @Test
+    fun doesNotThrowExceptionForFlickerTestFailureIfRequested() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule =
+            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = false)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+
+        val assertionError = Throwable("Some assertion error")
+        `when`(mockFlickerServiceResultsCollector.resultsForTest(mockDescription))
+            .thenReturn(listOf(mockFailureAssertionResult(assertionError)))
+        `when`(mockFlickerServiceResultsCollector.testContainsFlicker(mockDescription))
+            .thenReturn(true)
+
+        testRule.starting(mockDescription)
+        testRule.succeeded(mockDescription)
+        testRule.finished(mockDescription)
+    }
+
+    @Test
+    fun throwsExceptionForFlickerTestFailureIfRequested() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule =
+            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = true)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+
+        val assertionError = Throwable("Some assertion error")
+        `when`(mockFlickerServiceResultsCollector.resultsForTest(mockDescription))
+            .thenReturn(listOf(mockFailureAssertionResult(assertionError)))
+        `when`(mockFlickerServiceResultsCollector.testContainsFlicker(mockDescription))
+            .thenReturn(true)
+
+        testRule.starting(mockDescription)
+        testRule.succeeded(mockDescription)
+        try {
+            testRule.finished(mockDescription)
+            error("Exception was not thrown")
+        } catch (e: Throwable) {
+            Truth.assertThat(e).isEqualTo(assertionError)
+        }
+    }
+
+    @Test
+    fun alwaysThrowsExceptionForExecutionErrors() {
+        val mockFlickerServiceResultsCollector =
+            Mockito.mock(IFlickerServiceResultsCollector::class.java)
+        val testRule =
+            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = true)
+        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
+
+        val executionError = ExecutionError(Throwable(Consts.FAILURE))
+        `when`(mockFlickerServiceResultsCollector.executionErrors)
+            .thenReturn(listOf(executionError))
+
+        testRule.starting(mockDescription)
+        testRule.succeeded(mockDescription)
+        try {
+            testRule.finished(mockDescription)
+            error("Exception was not thrown")
+        } catch (e: Throwable) {
+            Truth.assertThat(e).isEqualTo(executionError)
+        }
+    }
+
+    companion object {
+        fun mockFailureAssertionResult(error: Throwable): IAssertionResult {
+            return AssertionResult(
+                object : IFaasAssertion {
+                    override val name: String
+                        get() = "MockAssertion"
+                    override val stabilityGroup: AssertionInvocationGroup
+                        get() = AssertionInvocationGroup.BLOCKING
+                    override fun evaluate(): IAssertionResult {
+                        error("Unimplemented - shouldn't be called")
+                    }
+                },
+                assertionError = error
+            )
+        }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTest.kt
new file mode 100644
index 0000000..b61e5e1
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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
+
+import android.app.Instrumentation
+import android.tools.InitRule
+import android.tools.common.flicker.assertors.IFaasAssertion
+import android.tools.common.flicker.assertors.factories.IAssertionFactory
+import android.tools.common.flicker.assertors.runners.IAssertionRunner
+import android.tools.common.flicker.extractors.IScenarioExtractor
+import android.tools.common.io.IReader
+import android.tools.device.flicker.FlickerService
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+
+/** Contains [FlickerService] tests. To run this test: `atest FlickerLibTest:FlickerServiceTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FlickerServiceTest {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
+
+    @Test
+    fun generatesAssertionsFromExtractedScenarios() {
+        val mockReader = Mockito.mock(IReader::class.java)
+        val mockScenarioExtractor = Mockito.mock(IScenarioExtractor::class.java)
+        val mockAssertionFactory = Mockito.mock(IAssertionFactory::class.java)
+        val mockAssertionRunner = Mockito.mock(IAssertionRunner::class.java)
+
+        val scenarioInstance = Mockito.mock(IScenarioInstance::class.java)
+        val assertions = listOf(Mockito.mock(IFaasAssertion::class.java))
+
+        Mockito.`when`(mockScenarioExtractor.extract(mockReader))
+            .thenReturn(listOf(scenarioInstance))
+        Mockito.`when`(mockAssertionFactory.generateAssertionsFor(scenarioInstance))
+            .thenReturn(assertions)
+
+        val service =
+            FlickerService(
+                scenarioExtractor = mockScenarioExtractor,
+                assertionFactory = mockAssertionFactory,
+                assertionRunner = mockAssertionRunner
+            )
+        service.process(mockReader)
+
+        Mockito.verify(mockScenarioExtractor).extract(mockReader)
+        Mockito.verify(mockAssertionFactory).generateAssertionsFor(scenarioInstance)
+    }
+
+    @Test
+    fun executesAssertionsReturnedByAssertionFactories() {
+        val mockReader = Mockito.mock(IReader::class.java)
+        val mockScenarioExtractor = Mockito.mock(IScenarioExtractor::class.java)
+        val mockAssertionFactory = Mockito.mock(IAssertionFactory::class.java)
+        val mockAssertionRunner = Mockito.mock(IAssertionRunner::class.java)
+
+        val scenarioInstance = Mockito.mock(IScenarioInstance::class.java)
+        val assertions = listOf(Mockito.mock(IFaasAssertion::class.java))
+
+        Mockito.`when`(mockScenarioExtractor.extract(mockReader))
+            .thenReturn(listOf(scenarioInstance))
+        Mockito.`when`(mockAssertionFactory.generateAssertionsFor(scenarioInstance))
+            .thenReturn(assertions)
+
+        val service =
+            FlickerService(
+                scenarioExtractor = mockScenarioExtractor,
+                assertionFactory = mockAssertionFactory,
+                assertionRunner = mockAssertionRunner
+            )
+        service.process(mockReader)
+
+        Mockito.verify(mockScenarioExtractor).extract(mockReader)
+        Mockito.verify(mockAssertionRunner).execute(assertions)
+    }
+
+    fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+    inline fun <reified T : Any> argumentCaptor() = ArgumentCaptor.forClass(T::class.java)
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTracesCollectorTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTracesCollectorTest.kt
new file mode 100644
index 0000000..cd6ff21
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/FlickerServiceTracesCollectorTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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
+
+import android.app.Instrumentation
+import android.tools.InitRule
+import android.tools.assertArchiveContainsFiles
+import android.tools.device.apphelpers.BrowserAppHelper
+import android.tools.device.flicker.FlickerServiceTracesCollector
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [FlickerServiceTracesCollector] tests. To run this test: `atest
+ * FlickerLibTest:FlickerServiceTracesCollectorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FlickerServiceTracesCollectorTest {
+    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp: BrowserAppHelper = BrowserAppHelper(instrumentation)
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+
+    @Test
+    fun canCollectTraces() {
+        val wmHelper = WindowManagerStateHelper(instrumentation)
+        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
+        collector.start()
+        testApp.launchViaIntent(wmHelper)
+        testApp.exit(wmHelper)
+        collector.stop()
+        val reader = collector.getResultReader()
+
+        Truth.assertThat(reader.readWmTrace()?.entries ?: emptyArray()).isNotEmpty()
+        Truth.assertThat(reader.readLayersTrace()?.entries ?: emptyArray()).isNotEmpty()
+        Truth.assertThat(reader.readTransitionsTrace()?.entries ?: emptyArray()).isNotEmpty()
+    }
+
+    @Test
+    fun reportsTraceFile() {
+        val wmHelper = WindowManagerStateHelper(instrumentation)
+        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
+        collector.start()
+        testApp.launchViaIntent(wmHelper)
+        testApp.exit(wmHelper)
+        collector.stop()
+        val tracePath = collector.getResultReader().artifactPath
+
+        require(tracePath.isNotEmpty()) { "Artifact path missing in result" }
+        val traceFile = File(tracePath)
+        Truth.assertThat(traceFile.exists()).isTrue()
+    }
+
+    @Test
+    fun reportedTraceFileContainsAllTraces() {
+        val wmHelper = WindowManagerStateHelper(instrumentation)
+        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
+        collector.start()
+        testApp.launchViaIntent(wmHelper)
+        testApp.exit(wmHelper)
+        collector.stop()
+        val tracePath = collector.getResultReader().artifactPath
+
+        require(tracePath.isNotEmpty()) { "Artifact path missing in result" }
+        val traceFile = File(tracePath)
+        assertArchiveContainsFiles(traceFile, expectedTraces)
+    }
+
+    companion object {
+        val expectedTraces =
+            listOf(
+                "wm_trace.winscope",
+                "layers_trace.winscope",
+                "transactions_trace.winscope",
+                "transition_trace.winscope",
+                "eventlog.winscope"
+            )
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/assertors/factories/AssertionFactoryTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/assertors/factories/AssertionFactoryTest.kt
new file mode 100644
index 0000000..716b78b
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/assertors/factories/AssertionFactoryTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.assertors.factories
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import android.tools.common.Rotation
+import android.tools.common.flicker.ScenarioInstance
+import android.tools.common.flicker.config.FaasScenarioType
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.common.io.IReader
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.Test
+import org.mockito.Mockito
+
+/**
+ * Contains tests for the [AssertionFactory]. To run this test: `atest
+ * FlickerLibTest:AssertionFactoryTest`
+ */
+class AssertionFactoryTest {
+
+    @Test
+    fun getsAssertionsFromConfig() {
+        val factory = AssertionFactory()
+
+        val type = FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_ICON
+
+        val scenarioInstance =
+            ScenarioInstance(
+                type = type,
+                startRotation = Rotation.ROTATION_0,
+                endRotation = Rotation.ROTATION_0,
+                startTimestamp = CrossPlatform.timestamp.min(),
+                endTimestamp = CrossPlatform.timestamp.max(),
+                reader = Mockito.mock(IReader::class.java)
+            )
+        val assertions = factory.generateAssertionsFor(scenarioInstance)
+
+        Truth.assertThat(assertions.map { it.name })
+            .containsExactlyElementsIn(
+                FlickerServiceConfig.getScenarioConfigFor(type = type).assertionTemplates.map {
+                    it.assertionName
+                }
+            )
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/events/EventLogSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/events/EventLogSubjectTest.kt
new file mode 100644
index 0000000..c20b47d
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/events/EventLogSubjectTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.events
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import android.tools.common.flicker.assertions.SubjectsParser
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.events.FocusEvent
+import android.tools.device.traces.io.ParsedTracesReader
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Contains [EventLogSubject] tests. To run this test: `atest FlickerLibTest:EventLogSubjectTest`
+ */
+class EventLogSubjectTest {
+    @Test
+    fun canDetectFocusChanges() {
+        val reader =
+            ParsedTracesReader(
+                eventLog =
+                    EventLog(
+                        arrayOf(
+                            FocusEvent(
+                                CrossPlatform.timestamp.from(unixNanos = 0),
+                                "WinB",
+                                FocusEvent.Type.GAINED,
+                                "test",
+                                0,
+                                "0",
+                                0
+                            ),
+                            FocusEvent(
+                                CrossPlatform.timestamp.from(unixNanos = 0),
+                                "test WinA window",
+                                FocusEvent.Type.LOST,
+                                "test",
+                                0,
+                                "0",
+                                0
+                            ),
+                            FocusEvent(
+                                CrossPlatform.timestamp.from(unixNanos = 0),
+                                "WinB",
+                                FocusEvent.Type.LOST,
+                                "test",
+                                0,
+                                "0",
+                                0
+                            ),
+                            FocusEvent(
+                                CrossPlatform.timestamp.from(unixNanos = 0),
+                                "test WinC",
+                                FocusEvent.Type.GAINED,
+                                "test",
+                                0,
+                                "0",
+                                0
+                            )
+                        )
+                    )
+            )
+        val subjectsParser = SubjectsParser(reader)
+
+        val subject = subjectsParser.eventLogSubject ?: error("Event log subject not built")
+        subject.focusChanges("WinA", "WinB", "WinC")
+        subject.focusChanges("WinA", "WinB")
+        subject.focusChanges("WinB", "WinC")
+        subject.focusChanges("WinA")
+        subject.focusChanges("WinB")
+        subject.focusChanges("WinC")
+    }
+
+    @Test
+    fun canDetectFocusDoesNotChange() {
+        val reader = ParsedTracesReader(eventLog = EventLog(emptyArray()))
+        val subjectsParser = SubjectsParser(reader)
+
+        val subject = subjectsParser.eventLogSubject ?: error("Event log subject not built")
+        subject.focusDoesNotChange()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/region/RegionSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/region/RegionSubjectTest.kt
new file mode 100644
index 0000000..9093cd6
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/region/RegionSubjectTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.region
+
+import android.tools.InitRule
+import android.tools.assertThrows
+import android.tools.common.CrossPlatform
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.subject.FlickerSubjectException
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [RegionSubject] tests. To run this test: `atest FlickerLibTest:RegionSubjectTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RegionSubjectTest {
+    private fun assertFail(expectedMessage: String, predicate: () -> Any) {
+        val error = assertThrows<FlickerSubjectException> { predicate() }
+        Truth.assertThat(error).hasMessageThat().contains(expectedMessage)
+    }
+
+    private fun expectAllFailPositionChange(expectedMessage: String, rectA: Rect, rectB: Rect) {
+        assertFail(expectedMessage) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
+        }
+        assertFail(expectedMessage) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
+        }
+        assertFail(expectedMessage) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
+        }
+        assertFail(expectedMessage) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
+        }
+    }
+
+    @Test
+    fun detectPositionChangeHigher() {
+        val rectA = Rect.from(left = 0, top = 0, right = 1, bottom = 1)
+        val rectB = Rect.from(left = 0, top = 1, right = 1, bottom = 2)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
+        }
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
+        }
+    }
+
+    @Test
+    fun detectPositionChangeLower() {
+        val rectA = Rect.from(left = 0, top = 2, right = 1, bottom = 3)
+        val rectB = Rect.from(left = 0, top = 0, right = 1, bottom = 1)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
+        }
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
+        }
+    }
+
+    @Test
+    fun detectPositionChangeEqualHigherLower() {
+        val rectA = Rect.from(left = 0, top = 1, right = 1, bottom = 0)
+        val rectB = Rect.from(left = 1, top = 1, right = 2, bottom = 0)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
+        }
+        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
+        }
+    }
+
+    @Test
+    fun detectPositionChangeInvalid() {
+        val rectA = Rect.from(left = 0, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectC = Rect.from(left = 0, top = 1, right = 3, bottom = 1)
+        expectAllFailPositionChange(RegionSubject.MSG_ERROR_LEFT_POSITION, rectA, rectB)
+        expectAllFailPositionChange(RegionSubject.MSG_ERROR_RIGHT_POSITION, rectA, rectC)
+    }
+
+    @Test
+    fun detectCoversAtLeast() {
+        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectA)
+        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectA)
+        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectB)
+        }
+    }
+
+    @Test
+    fun detectCoversAtMost() {
+        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectA)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectB)
+        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
+            RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectA)
+        }
+    }
+
+    @Test
+    fun detectCoversExactly() {
+        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversExactly(rectA)
+        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversExactly(rectB)
+        }
+    }
+
+    @Test
+    fun detectOverlaps() {
+        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
+        val rectC = Rect.from(left = 2, top = 2, right = 3, bottom = 3)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectB)
+        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectA)
+        assertFail("Overlap region: SkRegion()") {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectC)
+        }
+    }
+
+    @Test
+    fun detectsNotOverlaps() {
+        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
+        val rectB = Rect.from(left = 2, top = 2, right = 3, bottom = 3)
+        val rectC = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
+        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectB)
+        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectA)
+        assertFail("SkRegion((1,1,2,2))") {
+            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectC)
+        }
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerSubjectTest.kt
new file mode 100644
index 0000000..e40ceed
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerSubjectTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.common.datatypes.Size
+import android.tools.common.flicker.subject.layers.LayerSubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.readLayerTraceFromFile
+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 [LayerSubject] tests. To run this test: `atest FlickerLibTest:LayerSubjectTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayerSubjectTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfoImaginary() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val foundLayer = LayersTraceSubject(layersTraceEntries).first().layer("ImaginaryLayer", 0)
+        Truth.assertWithMessage("ImaginaryLayer is not found").that(foundLayer).isNull()
+    }
+
+    @Test
+    fun canTestAssertionsOnLayer() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        LayersTraceSubject(layersTraceEntries)
+            .layer("SoundVizWallpaperV2", 26033)
+            .hasBufferSize(Size.from(1440, 2960))
+            .hasScalingMode(0)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerTraceEntrySubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerTraceEntrySubjectTest.kt
new file mode 100644
index 0000000..a2f9a5b
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayerTraceEntrySubjectTest.kt
@@ -0,0 +1,403 @@
+/*
+ * 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.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.TestComponents
+import android.tools.assertFailureFact
+import android.tools.assertThatErrorContainsDebugInfo
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.OrComponentMatcher
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.readLayerTraceFromFile
+import android.tools.utils.MockLayerBuilder
+import android.tools.utils.MockLayerTraceEntryBuilder
+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 [LayerTraceEntrySubject] tests. To run this test: `atest
+ * FlickerLibTest:LayerTraceEntrySubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayerTraceEntrySubjectTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(layersTraceEntries)
+                    .first()
+                    .visibleRegion(TestComponents.IMAGINARY)
+            }
+        assertThatErrorContainsDebugInfo(error)
+        Truth.assertThat(error).hasMessageThat().contains(TestComponents.IMAGINARY.className)
+        Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
+    }
+
+    @Test
+    fun testCanInspectBeginning() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        LayerTraceEntrySubject(layersTraceEntries.entries.first())
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
+            .isVisible(TestComponents.LAUNCHER)
+    }
+
+    @Test
+    fun testCanInspectEnd() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        LayerTraceEntrySubject(layersTraceEntries.entries.last())
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
+    }
+
+    // b/75276931
+    @Test
+    fun canDetectUncoveredRegion() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val expectedRegion = Region.from(0, 0, 1440, 2960)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(935346112030, byElapsedTimestamp = true)
+                    .visibleRegion()
+                    .coversAtLeast(expectedRegion)
+            }
+        assertFailureFact(error, "Region to test").contains("SkRegion((0,0,1440,2960))")
+
+        assertFailureFact(error, "Uncovered region").contains("SkRegion((0,1440,1440,2960))")
+    }
+
+    // Visible region tests
+    @Test
+    fun canTestLayerVisibleRegion_layerDoesNotExist() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(937229257165, byElapsedTimestamp = true)
+                    .visibleRegion(TestComponents.IMAGINARY)
+                    .coversExactly(expectedVisibleRegion)
+            }
+        assertFailureFact(error, "Could not find layers")
+            .contains(TestComponents.IMAGINARY.toWindowIdentifier())
+    }
+
+    @Test
+    fun canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(937126074082, byElapsedTimestamp = true)
+                    .visibleRegion(TestComponents.DOCKER_STACK_DIVIDER)
+                    .coversExactly(expectedVisibleRegion)
+            }
+        assertFailureFact(error, "Covered region").contains("SkRegion()")
+    }
+
+    @Test
+    fun canTestLayerVisibleRegion_layerIsHiddenByParent() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(935346112030, byElapsedTimestamp = true)
+                    .visibleRegion(TestComponents.SIMPLE_APP)
+                    .coversExactly(expectedVisibleRegion)
+            }
+        assertFailureFact(error, "Covered region").contains("SkRegion()")
+    }
+
+    @Test
+    fun canTestLayerVisibleRegion_incorrectRegionSize() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val expectedVisibleRegion = Region.from(0, 0, 1440, 99)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(937126074082, byElapsedTimestamp = true)
+                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+                    .coversExactly(expectedVisibleRegion)
+            }
+        assertFailureFact(error, "Region to test").contains("SkRegion((0,0,1440,99))")
+    }
+
+    @Test
+    fun canTestLayerVisibleRegion() {
+        val trace =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        val expectedVisibleRegion = Region.from(0, 0, 1080, 145)
+        LayersTraceSubject(trace)
+            .getEntryBySystemUpTime(90480846872160, byElapsedTimestamp = true)
+            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+            .coversExactly(expectedVisibleRegion)
+    }
+
+    @Test
+    fun canTestLayerVisibleRegion_layerIsNotVisible() {
+        val trace =
+            readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb", legacyTrace = true)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .getEntryBySystemUpTime(252794268378458, byElapsedTimestamp = true)
+                    .isVisible(TestComponents.SIMPLE_APP)
+            }
+        assertFailureFact(error, "Invisibility reason", 1).contains("Bounds is 0x0")
+    }
+
+    @Test
+    fun orComponentMatcher_visibility_oneVisibleOtherInvisible() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry =
+            MockLayerTraceEntryBuilder()
+                .addDisplay(
+                    rootLayers =
+                        listOf(
+                            MockLayerBuilder(app1Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app1Name).setVisible()),
+                            MockLayerBuilder(app2Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app2Name).setInvisible()),
+                        )
+                )
+                .build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.isVisible(ComponentNameMatcher(app1Name))
+        subject.isInvisible(ComponentNameMatcher(app2Name))
+
+        subject.isInvisible(component)
+        subject.isVisible(component)
+    }
+
+    @Test
+    fun orComponentMatcher_visibility_oneVisibleOtherMissing() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry =
+            MockLayerTraceEntryBuilder()
+                .addDisplay(
+                    rootLayers =
+                        listOf(
+                            MockLayerBuilder(app1Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app1Name).setVisible())
+                        )
+                )
+                .build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.isVisible(ComponentNameMatcher(app1Name))
+        subject.notContains(ComponentNameMatcher(app2Name))
+
+        subject.isInvisible(component)
+        subject.isVisible(component)
+    }
+
+    @Test
+    fun canUseOrComponentMatcher_visibility_allVisible() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry =
+            MockLayerTraceEntryBuilder()
+                .addDisplay(
+                    rootLayers =
+                        listOf(
+                            MockLayerBuilder(app1Name)
+                                .setContainerLayer()
+                                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                                .addChild(MockLayerBuilder("$app1Name child").setVisible()),
+                            MockLayerBuilder(app2Name)
+                                .setContainerLayer()
+                                .setAbsoluteBounds(Rect.from(200, 200, 400, 400))
+                                .addChild(MockLayerBuilder("$app2Name child").setVisible()),
+                        )
+                )
+                .build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.isVisible(ComponentNameMatcher(app1Name))
+        subject.isVisible(ComponentNameMatcher(app2Name))
+
+        assertThrows<FlickerSubjectException> { subject.isInvisible(component) }
+        subject.isVisible(component)
+    }
+
+    @Test
+    fun canUseOrComponentMatcher_contains_withOneExists() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry =
+            MockLayerTraceEntryBuilder()
+                .addDisplay(
+                    rootLayers =
+                        listOf(
+                            MockLayerBuilder(app1Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app1Name))
+                        )
+                )
+                .build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.contains(ComponentNameMatcher(app1Name))
+        subject.notContains(ComponentNameMatcher(app2Name))
+
+        subject.notContains(component)
+        subject.contains(component)
+    }
+
+    @Test
+    fun canUseOrComponentMatcher_contains_withNoneExists() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry = MockLayerTraceEntryBuilder().addDisplay(rootLayers = listOf()).build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.notContains(ComponentNameMatcher(app1Name))
+        subject.notContains(ComponentNameMatcher(app2Name))
+
+        subject.notContains(component)
+        assertThrows<FlickerSubjectException> { subject.contains(component) }
+    }
+
+    @Test
+    fun canUseOrComponentMatcher_contains_withBothExists() {
+        val app1Name = "com.simple.test.app1"
+        val app2Name = "com.simple.test.app2"
+
+        val layerTraceEntry =
+            MockLayerTraceEntryBuilder()
+                .addDisplay(
+                    rootLayers =
+                        listOf(
+                            MockLayerBuilder(app1Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app1Name)),
+                            MockLayerBuilder(app2Name)
+                                .setContainerLayer()
+                                .addChild(MockLayerBuilder(app2Name)),
+                        )
+                )
+                .build()
+
+        val subject = LayerTraceEntrySubject(layerTraceEntry)
+        val component =
+            OrComponentMatcher(
+                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
+            )
+
+        subject.contains(ComponentNameMatcher(app1Name))
+        subject.contains(ComponentNameMatcher(app2Name))
+
+        assertThrows<FlickerSubjectException> { subject.notContains(component) }
+        subject.contains(component)
+    }
+
+    @Test
+    fun detectOccludedLayerBecauseOfRoundedCorners() {
+        val trace = readLayerTraceFromFile("layers_trace_rounded_corners.winscope")
+        val entry =
+            LayersTraceSubject(trace)
+                .getEntryBySystemUpTime(6216612368228, byElapsedTimestamp = true)
+        val defaultPkg = "com.android.server.wm.flicker.testapp"
+        val simpleActivityMatcher =
+            ComponentNameMatcher(defaultPkg, "$defaultPkg.SimpleActivity#66086")
+        val imeActivityMatcher = ComponentNameMatcher(defaultPkg, "$defaultPkg.ImeActivity#66060")
+        val simpleActivitySubject =
+            entry.layer(simpleActivityMatcher) ?: error("Layer should be available")
+        val imeActivitySubject =
+            entry.layer(imeActivityMatcher) ?: error("Layer should be available")
+        val simpleActivityLayer = simpleActivitySubject.layer
+        val imeActivityLayer = imeActivitySubject.layer
+        // both layers have the same region
+        imeActivitySubject.visibleRegion.coversExactly(simpleActivitySubject.visibleRegion.region)
+        // both are visible
+        entry.isInvisible(simpleActivityMatcher)
+        entry.isVisible(imeActivityMatcher)
+        // and simple activity is partially covered by IME activity
+        Truth.assertWithMessage("IME activity has rounded corners")
+            .that(simpleActivityLayer.occludedBy)
+            .asList()
+            .contains(imeActivityLayer)
+        // because IME activity has rounded corners
+        Truth.assertWithMessage("IME activity has rounded corners")
+            .that(imeActivityLayer.cornerRadius)
+            .isGreaterThan(0)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayersTraceSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayersTraceSubjectTest.kt
new file mode 100644
index 0000000..a587bda
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/surfaceflinger/LayersTraceSubjectTest.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.TestComponents
+import android.tools.assertFailureFact
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.readLayerTraceFromFile
+import androidx.test.filters.FlakyTest
+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 [LayersTraceSubject] tests. To run this test: `atest
+ * FlickerLibTest:LayersTraceSubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayersTraceSubjectTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        val error =
+            assertThrows<AssertionError> { LayersTraceSubject(layersTraceEntries).isEmpty() }
+        Truth.assertThat(error).hasMessageThat().contains("Trace start")
+        Truth.assertThat(error).hasMessageThat().contains("Trace end")
+    }
+
+    @Test
+    fun testCanDetectEmptyRegionFromLayerTrace() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(layersTraceEntries)
+                    .visibleRegion()
+                    .coversAtLeast(DISPLAY_REGION)
+                    .forAllEntries()
+                error("Assertion should not have passed")
+            }
+        assertFailureFact(failure, "Region to test").contains(DISPLAY_REGION.toString())
+        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,1440,1440,2880))")
+    }
+
+    @Test
+    fun testCanInspectBeginning() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        LayersTraceSubject(layersTraceEntries)
+            .first()
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
+            .isVisible(TestComponents.LAUNCHER)
+    }
+
+    @Test
+    fun testCanInspectEnd() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        LayersTraceSubject(layersTraceEntries)
+            .last()
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
+    }
+
+    @Test
+    fun testAssertionsOnRange() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+
+        LayersTraceSubject(layersTraceEntries)
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isInvisible(TestComponents.DOCKER_STACK_DIVIDER)
+            .forSystemUpTimeRange(90480846872160L, 90480994138424L)
+
+        LayersTraceSubject(layersTraceEntries)
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
+            .forSystemUpTimeRange(90491795074136L, 90493757372977L)
+    }
+
+    @Test
+    fun testCanDetectChangingAssertions() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        LayersTraceSubject(layersTraceEntries)
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
+            .then()
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isInvisible(TestComponents.DOCKER_STACK_DIVIDER)
+            .then()
+            .isVisible(ComponentNameMatcher.NAV_BAR)
+            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
+            .forAllEntries()
+    }
+
+    @FlakyTest
+    @Test
+    fun testCanDetectIncorrectVisibilityFromLayerTrace() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb", legacyTrace = true)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(layersTraceEntries)
+                    .isVisible(TestComponents.SIMPLE_APP)
+                    .then()
+                    .isInvisible(TestComponents.SIMPLE_APP)
+                    .forAllEntries()
+            }
+
+        Truth.assertThat(error)
+            .hasMessageThat()
+            .contains("layers_trace_invalid_layer_visibility.pb")
+        Truth.assertThat(error).hasMessageThat().contains("2d22h13m14s303ms")
+        Truth.assertThat(error).hasMessageThat().contains("!isVisible")
+        Truth.assertThat(error)
+            .hasMessageThat()
+            .contains(
+                "com.android.server.wm.flicker.testapp/" +
+                    "com.android.server.wm.flicker.testapp.SimpleActivity#0 is visible"
+            )
+    }
+
+    @Test
+    fun testCanDetectInvalidVisibleLayerForMoreThanOneConsecutiveEntry() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_invalid_visible_layers.pb", legacyTrace = true)
+        val error =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(layersTraceEntries)
+                    .visibleLayersShownMoreThanOneConsecutiveEntry()
+                    .forAllEntries()
+                error("Assertion should not have passed")
+            }
+
+        Truth.assertThat(error).hasMessageThat().contains("2d18h35m56s397ms")
+        Truth.assertThat(error).hasMessageThat().contains("StatusBar#0")
+        Truth.assertThat(error).hasMessageThat().contains("is not visible for 2 entries")
+    }
+
+    private fun testCanDetectVisibleLayersMoreThanOneConsecutiveEntry(trace: LayersTrace) {
+        LayersTraceSubject(trace).visibleLayersShownMoreThanOneConsecutiveEntry().forAllEntries()
+    }
+
+    @Test
+    fun testCanDetectVisibleLayersMoreThanOneConsecutiveEntry() {
+        testCanDetectVisibleLayersMoreThanOneConsecutiveEntry(
+            readLayerTraceFromFile("layers_trace_snapshot_visible.pb", legacyTrace = true)
+        )
+    }
+
+    @Test
+    fun testCanIgnoreLayerEqualNameInVisibleLayersMoreThanOneConsecutiveEntry() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_invalid_visible_layers.pb", legacyTrace = true)
+        LayersTraceSubject(layersTraceEntries)
+            .visibleLayersShownMoreThanOneConsecutiveEntry(listOf(ComponentNameMatcher.STATUS_BAR))
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanIgnoreLayerShorterNameInVisibleLayersMoreThanOneConsecutiveEntry() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("one_visible_layer_launcher_trace.pb", legacyTrace = true)
+        val launcherComponent =
+            ComponentNameMatcher(
+                "com.google.android.apps.nexuslauncher",
+                "com.google.android.apps.nexuslauncher.NexusLauncherActivity#1"
+            )
+        LayersTraceSubject(layersTraceEntries)
+            .visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherComponent))
+            .forAllEntries()
+    }
+
+    private fun detectRootLayer(fileName: String, legacyTrace: Boolean = false) {
+        val layersTrace = readLayerTraceFromFile(fileName, legacyTrace = legacyTrace)
+        for (entry in layersTrace.entries) {
+            val rootLayers = entry.children
+            Truth.assertWithMessage("Does not have any root layer")
+                .that(rootLayers.size)
+                .isGreaterThan(0)
+            val firstParentId = rootLayers.first().parentId
+            Truth.assertWithMessage("Has multiple root layers")
+                .that(rootLayers.all { it.parentId == firstParentId })
+                .isTrue()
+        }
+    }
+
+    @Test
+    fun testCanDetectRootLayer() {
+        detectRootLayer("layers_trace_root.pb", legacyTrace = true)
+    }
+
+    @Test
+    fun testCanDetectRootLayerAOSP() {
+        detectRootLayer("layers_trace_root_aosp.pb", legacyTrace = true)
+    }
+
+    @Test
+    fun canTestLayerOccludedBySplashScreenLayerIsNotVisible() {
+        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
+        val entry =
+            LayersTraceSubject(trace)
+                .getEntryBySystemUpTime(1700382131522L, byElapsedTimestamp = true)
+        entry.isInvisible(TestComponents.SIMPLE_APP)
+        entry.isVisible(ComponentNameMatcher.SPLASH_SCREEN)
+    }
+
+    @Test
+    fun testCanDetectLayerExpanding() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_openchrome.pb", legacyTrace = true)
+        val animation =
+            LayersTraceSubject(layersTraceEntries).layers("animation-leash of app_transition#0")
+        // Obtain the area of each layer and checks if the next area is
+        // greater or equal to the previous one
+        val areas =
+            animation.map {
+                val region = it.layer?.visibleRegion ?: Region()
+                val area = region.width * region.height
+                area
+            }
+        val expanding = areas.zipWithNext { currentArea, nextArea -> nextArea >= currentArea }
+
+        Truth.assertWithMessage("Animation leash should be expanding")
+            .that(expanding.all { it })
+            .isTrue()
+    }
+
+    @Test
+    fun checkVisibleRegionAppMinusPipLayer() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_pip_wmshell.pb", legacyTrace = true)
+        val subject = LayersTraceSubject(layersTraceEntries).last()
+
+        try {
+            subject.visibleRegion(TestComponents.FIXED_APP).coversExactly(DISPLAY_REGION_ROTATED)
+            error(
+                "Layer is partially covered by a Pip layer and should not cover the device screen"
+            )
+        } catch (e: AssertionError) {
+            val pipRegion = subject.visibleRegion(TestComponents.PIP_APP).region
+            val expectedWithoutPip = DISPLAY_REGION_ROTATED.minus(pipRegion)
+            subject.visibleRegion(TestComponents.FIXED_APP).coversExactly(expectedWithoutPip)
+        }
+    }
+
+    @Test
+    fun checkVisibleRegionAppPlusPipLayer() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_pip_wmshell.pb", legacyTrace = true)
+        val subject = LayersTraceSubject(layersTraceEntries).last()
+        val pipRegion = subject.visibleRegion(TestComponents.PIP_APP).region
+        subject
+            .visibleRegion(TestComponents.FIXED_APP)
+            .plus(pipRegion)
+            .coversExactly(DISPLAY_REGION_ROTATED)
+    }
+
+    @Test
+    fun checkCanDetectSplashScreen() {
+        val trace = readLayerTraceFromFile("layers_trace_splashscreen.pb", legacyTrace = true)
+        val newLayer =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.SimpleActivity"
+            )
+        LayersTraceSubject(trace)
+            .isVisible(TestComponents.LAUNCHER)
+            .then()
+            .isSplashScreenVisibleFor(TestComponents.SIMPLE_APP, isOptional = false)
+            .then()
+            .isVisible(TestComponents.SIMPLE_APP)
+            .forAllEntries()
+
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .isVisible(TestComponents.LAUNCHER)
+                    .then()
+                    .isVisible(TestComponents.SIMPLE_APP)
+                    .forAllEntries()
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("Is Invisible")
+    }
+
+    @Test
+    fun checkCanDetectMissingSplashScreen() {
+        val trace = readLayerTraceFromFile("layers_trace_splashscreen.pb", legacyTrace = true)
+        val newLayer =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.SimpleActivity"
+            )
+
+        // No splashscreen because no matching activity record
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace)
+                    .first()
+                    .isSplashScreenVisibleFor(TestComponents.SIMPLE_APP)
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("Could not find Activity Record layer")
+
+        // No splashscreen for target activity record
+        failure =
+            assertThrows<FlickerSubjectException> {
+                LayersTraceSubject(trace).first().isSplashScreenVisibleFor(TestComponents.LAUNCHER)
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("No splash screen visible")
+    }
+
+    companion object {
+        private val DISPLAY_REGION = Region.from(0, 0, 1440, 2880)
+        private val DISPLAY_REGION_ROTATED = Region.from(0, 0, 2160, 1080)
+        private const val SHELL_APP_PACKAGE = "com.android.wm.shell.flicker.testapp"
+        private val FIXED_APP =
+            ComponentNameMatcher(SHELL_APP_PACKAGE, "$SHELL_APP_PACKAGE.FixedActivity")
+        private val PIP_APP =
+            ComponentNameMatcher(SHELL_APP_PACKAGE, "$SHELL_APP_PACKAGE.PipActivity")
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubjectTest.kt
new file mode 100644
index 0000000..3163b02
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerStateSubjectTest.kt
@@ -0,0 +1,472 @@
+/*
+ * 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.InitRule
+import android.tools.TestComponents
+import android.tools.assertFailureFact
+import android.tools.assertThatErrorContainsDebugInfo
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.common.traces.wm.ConfigurationContainer
+import android.tools.common.traces.wm.KeyguardControllerState
+import android.tools.common.traces.wm.RootWindowContainer
+import android.tools.common.traces.wm.WindowContainer
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.readWmTraceFromDumpFile
+import android.tools.readWmTraceFromFile
+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 [WindowManagerStateSubject] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerStateSubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerStateSubjectTest {
+    private val trace
+        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
+
+    // Launcher is visible in fullscreen in the first frame of the trace
+    private val traceFirstFrameTimestamp = 9213763541297
+
+    // The first frame where the chrome splash screen is shown
+    private val traceFirstChromeFlashScreenTimestamp = 9215551505798
+
+    // The bounds of the display used to generate the trace [trace]
+    private val displayBounds = Region.from(0, 0, 1440, 2960)
+
+    // The region covered by the status bar in the trace
+    private val statusBarRegion = Region.from(0, 0, 1440, 171)
+
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val error =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerTraceSubject(trace).first().visibleRegion(TestComponents.IMAGINARY)
+            }
+        assertThatErrorContainsDebugInfo(error)
+        Truth.assertThat(error).hasMessageThat().contains(TestComponents.IMAGINARY.className)
+        Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
+    }
+
+    @Test
+    fun canDetectAboveAppWindowVisibility_isVisible() {
+        WindowManagerTraceSubject(trace)
+            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+            .containsAboveAppWindow(ComponentNameMatcher.NAV_BAR)
+            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
+            .containsAboveAppWindow(ComponentNameMatcher.STATUS_BAR)
+    }
+
+    @Test
+    fun canDetectAboveAppWindowVisibility_isInvisible() {
+        val subject =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .containsAboveAppWindow(TestComponents.PIP_OVERLAY)
+                    .isNonAppWindowVisible(TestComponents.PIP_OVERLAY)
+            }
+        assertFailureFact(failure, "Is Invisible").contains("pip-dismiss-overlay")
+
+        failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .containsAboveAppWindow(ComponentNameMatcher.NAV_BAR)
+                    .isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
+            }
+        assertFailureFact(failure, "Is Visible").contains("NavigationBar")
+    }
+
+    @Test
+    fun canDetectWindowCoversAtLeastRegion_exactSize() {
+        val entry =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+
+        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversAtLeast(statusBarRegion)
+        entry.visibleRegion(TestComponents.LAUNCHER).coversAtLeast(displayBounds)
+    }
+
+    @Test
+    fun canDetectWindowCoversAtLeastRegion_smallerRegion() {
+        val entry =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        entry
+            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+            .coversAtLeast(Region.from(0, 0, 100, 100))
+        entry.visibleRegion(TestComponents.LAUNCHER).coversAtLeast(Region.from(0, 0, 100, 100))
+    }
+
+    @Test
+    fun canDetectWindowCoversAtLeastRegion_largerRegion() {
+        val subject =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+                    .coversAtLeast(Region.from(0, 0, 1441, 171))
+            }
+        assertFailureFact(failure, "Uncovered region").contains("SkRegion((1440,0,1441,171))")
+
+        failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(TestComponents.LAUNCHER)
+                    .coversAtLeast(Region.from(0, 0, 1440, 2961))
+            }
+        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,2960,1440,2961))")
+    }
+
+    @Test
+    fun canDetectWindowCoversExactlyRegion_exactSize() {
+        val entry =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+
+        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(statusBarRegion)
+        entry.visibleRegion(TestComponents.LAUNCHER).coversExactly(displayBounds)
+    }
+
+    @Test
+    fun canDetectWindowCoversExactlyRegion_smallerRegion() {
+        val subject =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+                    .coversAtMost(Region.from(0, 0, 100, 100))
+            }
+        assertFailureFact(failure, "Out-of-bounds region")
+            .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
+
+        failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(TestComponents.LAUNCHER)
+                    .coversAtMost(Region.from(0, 0, 100, 100))
+            }
+        assertFailureFact(failure, "Out-of-bounds region")
+            .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
+    }
+
+    @Test
+    fun canDetectWindowCoversExactlyRegion_largerRegion() {
+        val subject =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+                    .coversAtLeast(Region.from(0, 0, 1441, 171))
+            }
+        assertFailureFact(failure, "Uncovered region").contains("SkRegion((1440,0,1441,171))")
+
+        failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(TestComponents.LAUNCHER)
+                    .coversAtLeast(Region.from(0, 0, 1440, 2961))
+            }
+        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,2960,1440,2961))")
+    }
+
+    @Test
+    fun canDetectWindowCoversAtMostRegion_extactSize() {
+        val entry =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversAtMost(statusBarRegion)
+        entry.visibleRegion(TestComponents.LAUNCHER).coversAtMost(displayBounds)
+    }
+
+    @Test
+    fun canDetectWindowCoversAtMostRegion_smallerRegion() {
+        val subject =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+        var failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+                    .coversAtMost(Region.from(0, 0, 100, 100))
+            }
+        assertFailureFact(failure, "Out-of-bounds region")
+            .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
+
+        failure =
+            assertThrows<FlickerSubjectException> {
+                subject
+                    .visibleRegion(TestComponents.LAUNCHER)
+                    .coversAtMost(Region.from(0, 0, 100, 100))
+            }
+        assertFailureFact(failure, "Out-of-bounds region")
+            .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
+    }
+
+    @Test
+    fun canDetectWindowCoversAtMostRegion_largerRegion() {
+        val entry =
+            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+
+        entry
+            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
+            .coversAtMost(Region.from(0, 0, 1441, 171))
+        entry.visibleRegion(TestComponents.LAUNCHER).coversAtMost(Region.from(0, 0, 1440, 2961))
+    }
+
+    @Test
+    fun canDetectBelowAppWindowVisibility() {
+        WindowManagerTraceSubject(trace)
+            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+            .containsNonAppWindow(TestComponents.WALLPAPER)
+    }
+
+    @Test
+    fun canDetectAppWindowVisibility() {
+        WindowManagerTraceSubject(trace)
+            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+            .containsAppWindow(TestComponents.LAUNCHER)
+
+        WindowManagerTraceSubject(trace)
+            .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
+            .containsAppWindow(TestComponents.CHROME_SPLASH_SCREEN)
+    }
+
+    @Test
+    fun canDetectAppWindowVisibilitySubject() {
+        val trace =
+            readWmTraceFromFile("wm_trace_launcher_visible_background.pb", legacyTrace = true)
+        val firstEntry = WindowManagerTraceSubject(trace).first()
+        val appWindowNames = firstEntry.wmState.appWindows.map { it.name }
+        val expectedAppWindowName =
+            "com.android.server.wm.flicker.testapp/" +
+                "com.android.server.wm.flicker.testapp.SimpleActivity"
+        firstEntry.check { "has1AppWindow" }.that(appWindowNames.size).isEqual(3)
+        firstEntry
+            .check { "App window names contain $expectedAppWindowName" }
+            .that(appWindowNames)
+            .contains(expectedAppWindowName)
+    }
+
+    @Test
+    fun canDetectLauncherVisibility() {
+        val trace =
+            readWmTraceFromFile("wm_trace_launcher_visible_background.pb", legacyTrace = true)
+        val subject = WindowManagerTraceSubject(trace)
+        val firstTrace = subject.first()
+        firstTrace.isAppWindowInvisible(TestComponents.LAUNCHER)
+
+        // in the trace there are 2 launcher windows, a visible (usually the main launcher) and
+        // an invisible one (the -1 window, for the swipe back on home screen action.
+        // in flicker, the launcher is considered visible is any of them is visible
+        subject.last().isAppWindowVisible(TestComponents.LAUNCHER)
+
+        subject
+            .isAppWindowNotOnTop(TestComponents.LAUNCHER)
+            .isAppWindowInvisible(TestComponents.LAUNCHER)
+            .then()
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .forAllEntries()
+    }
+
+    @Test
+    fun canFailWithReasonForVisibilityChecks_windowNotFound() {
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerTraceSubject(trace)
+                    .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+                    .containsNonAppWindow(TestComponents.IMAGINARY)
+            }
+        Truth.assertThat(failure).hasMessageThat().contains(TestComponents.IMAGINARY.packageName)
+    }
+
+    @Test
+    fun canFailWithReasonForVisibilityChecks_windowNotVisible() {
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerTraceSubject(trace)
+                    .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
+                    .containsNonAppWindow(ComponentNameMatcher.IME)
+                    .isNonAppWindowVisible(ComponentNameMatcher.IME)
+            }
+        assertFailureFact(failure, "Is Invisible").contains(ComponentNameMatcher.IME.packageName)
+    }
+
+    @Test
+    fun canDetectAppZOrder() {
+        WindowManagerTraceSubject(trace)
+            .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
+            .containsAppWindow(TestComponents.LAUNCHER)
+            .isAppWindowVisible(TestComponents.LAUNCHER)
+            .isAboveWindow(TestComponents.CHROME_SPLASH_SCREEN, TestComponents.LAUNCHER)
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+    }
+
+    @Test
+    fun canFailWithReasonForZOrderChecks_windowNotOnTop() {
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerTraceSubject(trace)
+                    .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
+                    .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
+            }
+        assertFailureFact(failure, "Found").contains(TestComponents.LAUNCHER.packageName)
+    }
+
+    @Test
+    fun canDetectActivityVisibility() {
+        val trace = readWmTraceFromFile("wm_trace_split_screen.pb", legacyTrace = true)
+        val lastEntry = WindowManagerTraceSubject(trace).last()
+        lastEntry.isAppWindowVisible(TestComponents.SHELL_SPLIT_SCREEN_PRIMARY)
+        lastEntry.isAppWindowVisible(TestComponents.SHELL_SPLIT_SCREEN_SECONDARY)
+    }
+
+    @Test
+    fun canHandleNoSubjects() {
+        val emptyRootContainer =
+            RootWindowContainer(
+                WindowContainer(
+                    title = "root",
+                    token = "",
+                    orientation = 0,
+                    layerId = 0,
+                    _isVisible = true,
+                    children = emptyArray(),
+                    configurationContainer = ConfigurationContainer(null, null, null),
+                    computedZ = 0
+                )
+            )
+        val noWindowsState =
+            WindowManagerState(
+                elapsedTimestamp = 0,
+                clockTimestamp = null,
+                where = "",
+                policy = null,
+                focusedApp = "",
+                focusedDisplayId = 0,
+                _focusedWindow = "",
+                inputMethodWindowAppToken = "",
+                isHomeRecentsComponent = false,
+                isDisplayFrozen = false,
+                _pendingActivities = emptyArray(),
+                root = emptyRootContainer,
+                keyguardControllerState =
+                    KeyguardControllerState.from(
+                        isAodShowing = false,
+                        isKeyguardShowing = false,
+                        keyguardOccludedStates = mapOf()
+                    )
+            )
+
+        val mockComponent = ComponentNameMatcher("", "Mock")
+
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerStateSubject(noWindowsState).isAppWindowOnTop(mockComponent)
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("No visible app windows found")
+    }
+
+    @Test
+    fun canDetectNoVisibleAppWindows() {
+        val trace = readWmTraceFromFile("wm_trace_unlock.pb", legacyTrace = true)
+        val firstEntry = WindowManagerTraceSubject(trace).first()
+        firstEntry.hasNoVisibleAppWindow()
+    }
+
+    @Test
+    fun canDetectHasVisibleAppWindows() {
+        val trace = readWmTraceFromFile("wm_trace_unlock.pb", legacyTrace = true)
+        val lastEntry = WindowManagerTraceSubject(trace).last()
+        val failure = assertThrows<FlickerSubjectException> { lastEntry.hasNoVisibleAppWindow() }
+        Truth.assertThat(failure).hasMessageThat().contains("Found visible windows")
+    }
+
+    @Test
+    fun canDetectTaskFragment() {
+        // Verify if parser can read a dump file with 2 TaskFragments showed side-by-side.
+        val trace = readWmTraceFromDumpFile("wm_trace_taskfragment.winscope")
+        // There's only one entry in dump file.
+        val entry = WindowManagerTraceSubject(trace).first()
+        // Verify there's exact 2 TaskFragments in window hierarchy.
+        Truth.assertThat(entry.wmState.taskFragments.size).isEqualTo(2)
+    }
+
+    @Test
+    fun canDetectIsHomeActivityVisibleTablet() {
+        val trace = readWmTraceFromDumpFile("tablet/wm_dump_home_screen.winscope")
+        // There's only one entry in dump file.
+        val entry = WindowManagerTraceSubject(trace).first()
+        // Verify that the device is in home screen
+        Truth.assertThat(entry.wmState.isHomeActivityVisible).isTrue()
+        // Verify that the subject is in home screen
+        entry.isHomeActivityVisible()
+    }
+
+    @Test
+    fun canDetectTaskBarIsVisible() {
+        val trace = readWmTraceFromDumpFile("tablet/wm_dump_home_screen.winscope")
+        // There's only one entry in dump file.
+        val entry = WindowManagerTraceSubject(trace).first()
+        // Verify that the taskbar is visible
+        entry.isNonAppWindowVisible(ComponentNameMatcher.TASK_BAR)
+    }
+
+    @Test
+    fun canDetectWindowVisibilityWhen2WindowsHaveSameName() {
+        val trace =
+            readWmTraceFromFile("wm_trace_2activities_same_name.winscope", legacyTrace = true)
+        val componentMatcher =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.NotificationActivity"
+            )
+        WindowManagerTraceSubject(trace)
+            .isAppWindowInvisible(componentMatcher)
+            .then()
+            .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+            .then()
+            .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+            .then()
+            .isAppWindowVisible(componentMatcher)
+            .forElapsedTimeRange(394872035003110L, 394874232110818L)
+    }
+
+    @Test
+    fun canDetectInvisibleWindowBecauseActivityIsInvisible() {
+        val entry = WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(9215551505798L)
+        entry.isAppWindowInvisible(TestComponents.CHROME_SPLASH_SCREEN)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubjectTest.kt
new file mode 100644
index 0000000..835b1fa
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowManagerTraceSubjectTest.kt
@@ -0,0 +1,279 @@
+/*
+ * 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.InitRule
+import android.tools.TestComponents
+import android.tools.assertThatErrorContainsDebugInfo
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.FlickerSubjectException
+import android.tools.readWmTraceFromFile
+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 [WindowManagerTraceSubject] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerTraceSubjectTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerTraceSubjectTest {
+    private val chromeTrace
+        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
+    private val imeTrace
+        get() = readWmTraceFromFile("wm_trace_ime.pb", legacyTrace = true)
+
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun testVisibleAppWindowForRange() {
+        WindowManagerTraceSubject(chromeTrace)
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .forElapsedTimeRange(9213763541297L, 9215536878453L)
+
+        WindowManagerTraceSubject(chromeTrace)
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .isAppWindowInvisible(TestComponents.CHROME_SPLASH_SCREEN)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
+            .isAppWindowInvisible(TestComponents.LAUNCHER)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
+            .isAppWindowInvisible(TestComponents.LAUNCHER)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .forElapsedTimeRange(9215551505798L, 9216093628925L)
+    }
+
+    @Test
+    fun testCanTransitionInAppWindow() {
+        WindowManagerTraceSubject(chromeTrace)
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanDetectTransitionWithOptionalValue() {
+        val trace = readWmTraceFromFile("wm_trace_open_from_overview.pb", legacyTrace = true)
+        val subject = WindowManagerTraceSubject(trace)
+        subject
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .then()
+            .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
+    }
+
+    @Test
+    fun testCanTransitionInAppWindow_withOptional() {
+        WindowManagerTraceSubject(chromeTrace)
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .then()
+            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
+            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanInspectBeginning() {
+        WindowManagerTraceSubject(chromeTrace)
+            .first()
+            .isAppWindowOnTop(TestComponents.LAUNCHER)
+            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
+    }
+
+    @Test
+    fun testCanInspectAppWindowOnTop() {
+        WindowManagerTraceSubject(chromeTrace).first().isAppWindowOnTop(TestComponents.LAUNCHER)
+
+        val failure =
+            assertThrows<FlickerSubjectException> {
+                WindowManagerTraceSubject(chromeTrace)
+                    .first()
+                    .isAppWindowOnTop(TestComponents.IMAGINARY)
+                    .fail("Could not detect the top app window")
+            }
+        Truth.assertThat(failure).hasMessageThat().contains("ImaginaryWindow")
+    }
+
+    @Test
+    fun testCanInspectEnd() {
+        WindowManagerTraceSubject(chromeTrace)
+            .last()
+            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
+            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
+    }
+
+    @Test
+    fun testCanTransitionNonAppWindow() {
+        WindowManagerTraceSubject(imeTrace)
+            .skipUntilFirstAssertion()
+            .isNonAppWindowInvisible(ComponentNameMatcher.IME)
+            .then()
+            .isNonAppWindowVisible(ComponentNameMatcher.IME)
+            .forAllEntries()
+    }
+
+    @Test(expected = AssertionError::class)
+    fun testCanDetectOverlappingWindows() {
+        WindowManagerTraceSubject(imeTrace)
+            .doNotOverlap(
+                ComponentNameMatcher.IME,
+                ComponentNameMatcher.NAV_BAR,
+                TestComponents.IME_ACTIVITY
+            )
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanTransitionAboveAppWindow() {
+        WindowManagerTraceSubject(imeTrace)
+            .skipUntilFirstAssertion()
+            .isAboveAppWindowInvisible(ComponentNameMatcher.IME)
+            .then()
+            .isAboveAppWindowVisible(ComponentNameMatcher.IME)
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanTransitionBelowAppWindow() {
+        val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb", legacyTrace = true)
+        WindowManagerTraceSubject(trace)
+            .skipUntilFirstAssertion()
+            .isBelowAppWindowVisible(TestComponents.WALLPAPER)
+            .then()
+            .isBelowAppWindowInvisible(TestComponents.WALLPAPER)
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanDetectVisibleWindowsMoreThanOneConsecutiveEntry() {
+        val trace = readWmTraceFromFile("wm_trace_valid_visible_windows.pb", legacyTrace = true)
+        WindowManagerTraceSubject(trace)
+            .visibleWindowsShownMoreThanOneConsecutiveEntry()
+            .forAllEntries()
+    }
+
+    @Test
+    fun testCanAssertWindowStateSequence() {
+        val componentMatcher =
+            ComponentNameMatcher.unflattenFromString(
+                "com.android.chrome/org.chromium.chrome.browser.firstrun.FirstRunActivity"
+            )
+        val windowStates = WindowManagerTraceSubject(chromeTrace).windowStates(componentMatcher)
+
+        val visibilityChange =
+            windowStates.zipWithNext { current, next ->
+                current.windowState?.isVisible != next.windowState?.isVisible
+            }
+
+        Truth.assertWithMessage("Visibility should have changed only 1x in the trace")
+            .that(visibilityChange.count { it })
+            .isEqualTo(1)
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val error =
+            assertThrows<AssertionError> { WindowManagerTraceSubject(chromeTrace).isEmpty() }
+        assertThatErrorContainsDebugInfo(error, withBlameEntry = false)
+    }
+
+    @Test
+    fun testCanDetectSnapshotStartingWindow() {
+        val trace =
+            readWmTraceFromFile(
+                "quick_switch_to_app_killed_in_background_trace.pb",
+                legacyTrace = true
+            )
+        val app1 =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.ImeActivity"
+            )
+        val app2 =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.SimpleActivity"
+            )
+        WindowManagerTraceSubject(trace)
+            .isAppWindowVisible(app1)
+            .then()
+            .isAppSnapshotStartingWindowVisibleFor(app2, isOptional = true)
+            .then()
+            .isAppWindowVisible(app2)
+            .then()
+            .isAppSnapshotStartingWindowVisibleFor(app1, isOptional = true)
+            .then()
+            .isAppWindowVisible(app1)
+            .forAllEntries()
+    }
+
+    @Test
+    fun canDetectAppInvisibleSnapshotStartingWindowVisible() {
+        val trace =
+            readWmTraceFromFile(
+                "quick_switch_to_app_killed_in_background_trace.pb",
+                legacyTrace = true
+            )
+        val subject = WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(694827105830L)
+        val app =
+            ComponentNameMatcher(
+                "com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.SimpleActivity"
+            )
+        subject.isAppWindowInvisible(app)
+        subject.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT)
+    }
+
+    @Test
+    fun canDetectAppVisibleTablet() {
+        val trace = readWmTraceFromFile("tablet/wm_trace_open_chrome.winscope", legacyTrace = true)
+        WindowManagerTraceSubject(trace).isAppWindowVisible(TestComponents.CHROME).forAllEntries()
+    }
+
+    @Test
+    fun canDetectAppOpenRecentsTablet() {
+        val trace = readWmTraceFromFile("tablet/wm_trace_open_recents.winscope", legacyTrace = true)
+        WindowManagerTraceSubject(trace).isRecentsActivityVisible().forAllEntries()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowStateSubjectTest.kt b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowStateSubjectTest.kt
new file mode 100644
index 0000000..d4bd1d3
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/flicker/subject/wm/WindowStateSubjectTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.InitRule
+import android.tools.TestComponents
+import android.tools.common.Cache
+import android.tools.readWmTraceFromFile
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+class WindowStateSubjectTest {
+    private val trace
+        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
+
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfoImaginary() {
+        val foundWindow =
+            WindowManagerTraceSubject(trace).first().windowState(TestComponents.IMAGINARY.className)
+        Truth.assertWithMessage("${TestComponents.IMAGINARY.className} is not found")
+            .that(foundWindow)
+            .isNull()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/io/ResultArtifactDescriptorTest.kt b/libraries/flicker/test/src/android/tools/common/io/ResultArtifactDescriptorTest.kt
new file mode 100644
index 0000000..08042d9
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/io/ResultArtifactDescriptorTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.io
+
+import android.tools.InitRule
+import android.tools.common.Tag
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Tests for [ResultArtifactDescriptor] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ResultArtifactDescriptorTest {
+    @Test
+    fun generateDescriptorFromTrace() {
+        createDescriptorAndValidateFileName(TraceType.SF)
+        createDescriptorAndValidateFileName(TraceType.WM)
+        createDescriptorAndValidateFileName(TraceType.TRANSACTION)
+        createDescriptorAndValidateFileName(TraceType.TRANSACTION)
+        createDescriptorAndValidateFileName(TraceType.SCREEN_RECORDING)
+        createDescriptorAndValidateFileName(TraceType.WM_DUMP)
+        createDescriptorAndValidateFileName(TraceType.SF_DUMP)
+    }
+
+    @Test
+    fun generateDescriptorFromTraceWithTags() {
+        createDescriptorAndValidateFileNameWithTag(TraceType.SF)
+        createDescriptorAndValidateFileNameWithTag(TraceType.WM)
+        createDescriptorAndValidateFileNameWithTag(TraceType.TRANSACTION)
+        createDescriptorAndValidateFileNameWithTag(TraceType.TRANSACTION)
+        createDescriptorAndValidateFileNameWithTag(TraceType.SCREEN_RECORDING)
+        createDescriptorAndValidateFileNameWithTag(TraceType.WM_DUMP)
+        createDescriptorAndValidateFileNameWithTag(TraceType.SF_DUMP)
+    }
+
+    @Test
+    fun parseDescriptorFromFileName() {
+        parseDescriptorAndValidateType(TraceType.SF.fileName, TraceType.SF)
+        parseDescriptorAndValidateType(TraceType.WM.fileName, TraceType.WM)
+        parseDescriptorAndValidateType(TraceType.TRANSACTION.fileName, TraceType.TRANSACTION)
+        parseDescriptorAndValidateType(TraceType.TRANSACTION.fileName, TraceType.TRANSACTION)
+        parseDescriptorAndValidateType(
+            TraceType.SCREEN_RECORDING.fileName,
+            TraceType.SCREEN_RECORDING
+        )
+        parseDescriptorAndValidateType(TraceType.WM_DUMP.fileName, TraceType.WM_DUMP)
+        parseDescriptorAndValidateType(TraceType.SF_DUMP.fileName, TraceType.SF_DUMP)
+    }
+
+    @Test
+    fun parseDescriptorFromFileNameWithTags() {
+        parseDescriptorAndValidateType(buildTaggedName(TraceType.SF), TraceType.SF, TEST_TAG)
+        parseDescriptorAndValidateType(buildTaggedName(TraceType.WM), TraceType.WM, TEST_TAG)
+        parseDescriptorAndValidateType(
+            buildTaggedName(TraceType.TRANSACTION),
+            TraceType.TRANSACTION,
+            TEST_TAG
+        )
+        parseDescriptorAndValidateType(
+            buildTaggedName(TraceType.TRANSACTION),
+            TraceType.TRANSACTION,
+            TEST_TAG
+        )
+        parseDescriptorAndValidateType(
+            buildTaggedName(TraceType.SCREEN_RECORDING),
+            TraceType.SCREEN_RECORDING,
+            TEST_TAG
+        )
+        parseDescriptorAndValidateType(
+            buildTaggedName(TraceType.WM_DUMP),
+            TraceType.WM_DUMP,
+            TEST_TAG
+        )
+        parseDescriptorAndValidateType(
+            buildTaggedName(TraceType.SF_DUMP),
+            TraceType.SF_DUMP,
+            TEST_TAG
+        )
+    }
+
+    private fun buildTaggedName(traceType: TraceType): String =
+        ResultArtifactDescriptor(traceType, TEST_TAG).fileNameInArtifact
+
+    private fun parseDescriptorAndValidateType(
+        fileNameInArtifact: String,
+        expectedTraceType: TraceType,
+        expectedTag: String = Tag.ALL
+    ): ResultArtifactDescriptor {
+        val descriptor = ResultArtifactDescriptor.fromFileName(fileNameInArtifact)
+        Truth.assertWithMessage("Descriptor type")
+            .that(descriptor.traceType)
+            .isEqualTo(expectedTraceType)
+        Truth.assertWithMessage("Descriptor tag").that(descriptor.tag).isEqualTo(expectedTag)
+        return descriptor
+    }
+
+    private fun createDescriptorAndValidateFileName(traceType: TraceType) {
+        val descriptor = ResultArtifactDescriptor(traceType)
+        Truth.assertWithMessage("Result file name")
+            .that(descriptor.fileNameInArtifact)
+            .isEqualTo(traceType.fileName)
+    }
+
+    private fun createDescriptorAndValidateFileNameWithTag(traceType: TraceType) {
+        val tag = "testTag"
+        val descriptor = ResultArtifactDescriptor(traceType, TEST_TAG)
+        val subject =
+            Truth.assertWithMessage("Result file name").that(descriptor.fileNameInArtifact)
+        subject.startsWith(tag)
+        subject.endsWith(traceType.fileName)
+    }
+
+    companion object {
+        private const val TEST_TAG = "testTag"
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/io/TraceTypeTest.kt b/libraries/flicker/test/src/android/tools/common/io/TraceTypeTest.kt
new file mode 100644
index 0000000..d76cf58
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/io/TraceTypeTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.io
+
+import android.tools.InitRule
+import android.tools.assertThrows
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Tests for [TraceType] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TraceTypeTest {
+    @Test
+    fun canParseTraceTypes() {
+        assertFileName(TraceType.SF)
+        assertFileName(TraceType.WM)
+        assertFileName(TraceType.TRANSACTION)
+        assertFileName(TraceType.TRANSITION)
+        assertFileName(TraceType.SCREEN_RECORDING)
+    }
+
+    @Test
+    fun canParseDumpTypes() {
+        assertFileName(TraceType.SF_DUMP)
+        assertFileName(TraceType.WM_DUMP)
+        assertFileName(
+            TraceType.SF_DUMP,
+            TraceType.fromFileName("prefix${TraceType.SF_DUMP.fileName}")
+        )
+        assertFileName(
+            TraceType.WM_DUMP,
+            TraceType.fromFileName("prefix${TraceType.WM_DUMP.fileName}")
+        )
+    }
+
+    @Test
+    fun failParseInvalidTypes() {
+        assertFailure("prefix${TraceType.SF.fileName}")
+        assertFailure("prefix${TraceType.WM.fileName}")
+        assertFailure("prefix${TraceType.TRANSACTION.fileName}")
+        assertFailure("prefix${TraceType.TRANSITION.fileName}")
+        assertFailure("prefix${TraceType.SCREEN_RECORDING.fileName}")
+        assertFailure("${TraceType.SF_DUMP.fileName}suffix")
+        assertFailure("${TraceType.WM_DUMP.fileName}suffix")
+    }
+
+    private fun assertFailure(fileName: String) {
+        assertThrows<IllegalStateException> { TraceType.fromFileName(fileName) }
+    }
+
+    private fun assertFileName(
+        type: TraceType,
+        newInstance: TraceType = TraceType.fromFileName(type.fileName)
+    ) {
+        Truth.assertWithMessage("Trace type matches file name").that(newInstance).isEqualTo(type)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/ITraceTest.kt b/libraries/flicker/test/src/android/tools/common/traces/ITraceTest.kt
new file mode 100644
index 0000000..e351c48
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/ITraceTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.traces
+
+import android.tools.assertThrows
+import android.tools.common.CrossPlatform
+import android.tools.common.ITrace
+import android.tools.common.ITraceEntry
+import android.tools.common.Timestamp
+import com.google.common.truth.Truth
+import org.junit.Test
+
+/** To run this test: `atest FlickerLibTest:ITraceTest` */
+class ITraceTest {
+    @Test
+    fun getEntryExactlyAtTest() {
+        val entry1 = SimpleTraceEntry(CrossPlatform.timestamp.from(1, 1, 1))
+        val entry2 = SimpleTraceEntry(CrossPlatform.timestamp.from(5, 5, 5))
+        val entry3 = SimpleTraceEntry(CrossPlatform.timestamp.from(25, 25, 25))
+        val trace = SimpleTrace(arrayOf(entry1, entry2, entry3))
+
+        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(1, 1, 1)))
+            .isEqualTo(entry1)
+        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(5, 5, 5)))
+            .isEqualTo(entry2)
+        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(25, 25, 25)))
+            .isEqualTo(entry3)
+
+        Truth.assertThat(
+                assertThrows<Throwable> {
+                    trace.getEntryExactlyAt(CrossPlatform.timestamp.from(6, 6, 6))
+                }
+            )
+            .hasMessageThat()
+            .contains("does not exist")
+    }
+
+    @Test
+    fun getEntryAtTest() {
+        val entry1 = SimpleTraceEntry(CrossPlatform.timestamp.from(2, 2, 2))
+        val entry2 = SimpleTraceEntry(CrossPlatform.timestamp.from(5, 5, 5))
+        val entry3 = SimpleTraceEntry(CrossPlatform.timestamp.from(25, 25, 25))
+        val trace = SimpleTrace(arrayOf(entry1, entry2, entry3))
+
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(2, 2, 2))).isEqualTo(entry1)
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(5, 5, 5))).isEqualTo(entry2)
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(25, 25, 25)))
+            .isEqualTo(entry3)
+
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(4, 4, 4))).isEqualTo(entry1)
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(6, 6, 6))).isEqualTo(entry2)
+        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(100, 100, 100)))
+            .isEqualTo(entry3)
+
+        Truth.assertThat(
+                assertThrows<Throwable> { trace.getEntryAt(CrossPlatform.timestamp.from(1, 1, 1)) }
+            )
+            .hasMessageThat()
+            .contains("No entry at or before timestamp")
+    }
+
+    class SimpleTraceEntry(override val timestamp: Timestamp) : ITraceEntry
+
+    class SimpleTrace(override val entries: Array<ITraceEntry>) : ITrace<ITraceEntry> {
+        override fun slice(
+            startTimestamp: Timestamp,
+            endTimestamp: Timestamp
+        ): ITrace<ITraceEntry> {
+            TODO("Not yet implemented")
+        }
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/events/CujTraceTest.kt b/libraries/flicker/test/src/android/tools/common/traces/events/CujTraceTest.kt
new file mode 100644
index 0000000..99705aa
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/events/CujTraceTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.traces.events
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [CujTrace]. Run with `atest FlickerLibTest:CujTraceTest` */
+class CujTraceTest {
+    @Test
+    fun canCreateFromArrayOfCujEvents() {
+        val trace =
+            CujTrace.from(
+                arrayOf(
+                    createCujEvent(
+                        1,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_BEGIN_TAG
+                    ),
+                    createCujEvent(
+                        2,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_END_TAG
+                    ),
+                )
+            )
+
+        Truth.assertThat(trace.entries).hasLength(1)
+        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
+        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
+        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
+        Truth.assertThat(trace.entries[0].canceled).isFalse()
+    }
+
+    @Test
+    fun canCreateCanceledCujsFromArrayOfCujEvents() {
+        val trace =
+            CujTrace.from(
+                arrayOf(
+                    createCujEvent(
+                        1,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_BEGIN_TAG
+                    ),
+                    createCujEvent(
+                        2,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_CANCEL_TAG
+                    ),
+                )
+            )
+
+        Truth.assertThat(trace.entries).hasLength(1)
+        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
+        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
+        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
+        Truth.assertThat(trace.entries[0].canceled).isTrue()
+    }
+
+    @Test
+    fun canHandleIncompleteCujs() {
+        val trace =
+            CujTrace.from(
+                arrayOf(
+                    createCujEvent(
+                        1,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_CANCEL_TAG
+                    ),
+                    createCujEvent(
+                        2,
+                        CujType.CUJ_BIOMETRIC_PROMPT_TRANSITION,
+                        CujEvent.JANK_CUJ_END_TAG
+                    ),
+                    createCujEvent(
+                        3,
+                        CujType.CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+                        CujEvent.JANK_CUJ_BEGIN_TAG
+                    ),
+                )
+            )
+
+        Truth.assertThat(trace.entries).hasLength(0)
+    }
+
+    @Test
+    fun canHandleOutOfOrderEntries() {
+        val trace =
+            CujTrace.from(
+                arrayOf(
+                    createCujEvent(
+                        2,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_END_TAG
+                    ),
+                    createCujEvent(
+                        1,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_BEGIN_TAG
+                    ),
+                )
+            )
+
+        Truth.assertThat(trace.entries).hasLength(1)
+        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
+        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
+        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
+        Truth.assertThat(trace.entries[0].canceled).isFalse()
+    }
+
+    @Test
+    fun canHandleMissingStartAndEnds() {
+        val trace =
+            CujTrace.from(
+                arrayOf(
+                    createCujEvent(
+                        1,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_END_TAG
+                    ),
+                    createCujEvent(
+                        2,
+                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
+                        CujEvent.JANK_CUJ_BEGIN_TAG
+                    ),
+                )
+            )
+
+        Truth.assertThat(trace.entries).hasLength(0)
+    }
+
+    private fun createCujEvent(timestamp: Long, cuj: CujType, tag: String): CujEvent {
+        return CujEvent(CrossPlatform.timestamp.from(unixNanos = timestamp), cuj, 0, "root", 0, tag)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/region/RegionTest.kt b/libraries/flicker/test/src/android/tools/common/traces/region/RegionTest.kt
new file mode 100644
index 0000000..60d2d32
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/region/RegionTest.kt
@@ -0,0 +1,1790 @@
+/*
+ * 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.traces.region
+
+import android.tools.InitRule
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import kotlin.random.Random
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [Region] tests. To run this test: `atest FlickerLibTest:RegionTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RegionTest {
+    // DIFFERENCE
+    private val DIFFERENCE_WITH1 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(4, 4),
+            intArrayOf(10, 10),
+            intArrayOf(19, 19),
+            intArrayOf(19, 0),
+            intArrayOf(10, 4),
+            intArrayOf(4, 10),
+            intArrayOf(0, 19)
+        )
+    private val DIFFERENCE_WITHOUT1 =
+        arrayOf(intArrayOf(5, 5), intArrayOf(9, 9), intArrayOf(9, 5), intArrayOf(5, 9))
+
+    private val DIFFERENCE_WITH2 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(19, 0),
+            intArrayOf(9, 9),
+            intArrayOf(19, 9),
+            intArrayOf(0, 19),
+            intArrayOf(9, 19)
+        )
+    private val DIFFERENCE_WITHOUT2 =
+        arrayOf(
+            intArrayOf(10, 10),
+            intArrayOf(19, 10),
+            intArrayOf(10, 19),
+            intArrayOf(19, 19),
+            intArrayOf(29, 10),
+            intArrayOf(29, 29),
+            intArrayOf(10, 29)
+        )
+
+    private val DIFFERENCE_WITH3 =
+        arrayOf(intArrayOf(0, 0), intArrayOf(19, 0), intArrayOf(0, 19), intArrayOf(19, 19))
+    private val DIFFERENCE_WITHOUT3 =
+        arrayOf(intArrayOf(40, 40), intArrayOf(40, 59), intArrayOf(59, 40), intArrayOf(59, 59))
+
+    // INTERSECT
+    private val INTERSECT_WITH1 =
+        arrayOf(intArrayOf(5, 5), intArrayOf(9, 9), intArrayOf(9, 5), intArrayOf(5, 9))
+    private val INTERSECT_WITHOUT1 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(4, 4),
+            intArrayOf(10, 10),
+            intArrayOf(19, 19),
+            intArrayOf(19, 0),
+            intArrayOf(10, 4),
+            intArrayOf(4, 10),
+            intArrayOf(0, 19)
+        )
+
+    private val INTERSECT_WITH2 =
+        arrayOf(intArrayOf(10, 10), intArrayOf(19, 10), intArrayOf(10, 19), intArrayOf(19, 19))
+    private val INTERSECT_WITHOUT2 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(19, 0),
+            intArrayOf(9, 9),
+            intArrayOf(19, 9),
+            intArrayOf(0, 19),
+            intArrayOf(9, 19),
+            intArrayOf(29, 10),
+            intArrayOf(29, 29),
+            intArrayOf(10, 29)
+        )
+
+    // UNION
+    private val UNION_WITH1 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(4, 4),
+            intArrayOf(6, 6),
+            intArrayOf(10, 10),
+            intArrayOf(19, 19),
+            intArrayOf(19, 0),
+            intArrayOf(10, 4),
+            intArrayOf(4, 10),
+            intArrayOf(0, 19),
+            intArrayOf(5, 5),
+            intArrayOf(9, 9),
+            intArrayOf(9, 5),
+            intArrayOf(5, 9)
+        )
+    private val UNION_WITHOUT1 = arrayOf(intArrayOf(0, 20), intArrayOf(20, 20), intArrayOf(20, 0))
+
+    private val UNION_WITH2 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(19, 0),
+            intArrayOf(9, 9),
+            intArrayOf(19, 9),
+            intArrayOf(0, 19),
+            intArrayOf(9, 19),
+            intArrayOf(21, 21),
+            intArrayOf(10, 10),
+            intArrayOf(19, 10),
+            intArrayOf(10, 19),
+            intArrayOf(19, 19),
+            intArrayOf(29, 10),
+            intArrayOf(29, 29),
+            intArrayOf(10, 29)
+        )
+    private val UNION_WITHOUT2 =
+        arrayOf(
+            intArrayOf(0, 29),
+            intArrayOf(0, 20),
+            intArrayOf(9, 29),
+            intArrayOf(9, 20),
+            intArrayOf(29, 0),
+            intArrayOf(20, 0),
+            intArrayOf(29, 9),
+            intArrayOf(20, 9)
+        )
+
+    private val UNION_WITH3 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(19, 0),
+            intArrayOf(0, 19),
+            intArrayOf(19, 19),
+            intArrayOf(40, 40),
+            intArrayOf(41, 41),
+            intArrayOf(40, 59),
+            intArrayOf(59, 40),
+            intArrayOf(59, 59)
+        )
+    private val UNION_WITHOUT3 = arrayOf(intArrayOf(20, 20), intArrayOf(39, 39))
+
+    // XOR
+    private val XOR_WITH1 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(4, 4),
+            intArrayOf(10, 10),
+            intArrayOf(19, 19),
+            intArrayOf(19, 0),
+            intArrayOf(10, 4),
+            intArrayOf(4, 10),
+            intArrayOf(0, 19)
+        )
+    private val XOR_WITHOUT1 =
+        arrayOf(
+            intArrayOf(5, 5),
+            intArrayOf(6, 6),
+            intArrayOf(9, 9),
+            intArrayOf(9, 5),
+            intArrayOf(5, 9)
+        )
+
+    private val XOR_WITH2 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(19, 0),
+            intArrayOf(9, 9),
+            intArrayOf(19, 9),
+            intArrayOf(0, 19),
+            intArrayOf(9, 19),
+            intArrayOf(21, 21),
+            intArrayOf(29, 10),
+            intArrayOf(10, 29),
+            intArrayOf(20, 10),
+            intArrayOf(10, 20),
+            intArrayOf(20, 20),
+            intArrayOf(29, 29)
+        )
+    private val XOR_WITHOUT2 =
+        arrayOf(
+            intArrayOf(10, 10),
+            intArrayOf(11, 11),
+            intArrayOf(19, 10),
+            intArrayOf(10, 19),
+            intArrayOf(19, 19)
+        )
+
+    private val XOR_WITH3 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(2, 2),
+            intArrayOf(19, 0),
+            intArrayOf(0, 19),
+            intArrayOf(19, 19),
+            intArrayOf(40, 40),
+            intArrayOf(41, 41),
+            intArrayOf(40, 59),
+            intArrayOf(59, 40),
+            intArrayOf(59, 59)
+        )
+    private val XOR_WITHOUT3 = arrayOf(intArrayOf(20, 20), intArrayOf(39, 39))
+
+    // REVERSE_DIFFERENCE
+    private val REVERSE_DIFFERENCE_WITH2 =
+        arrayOf(
+            intArrayOf(29, 10),
+            intArrayOf(10, 29),
+            intArrayOf(20, 10),
+            intArrayOf(10, 20),
+            intArrayOf(20, 20),
+            intArrayOf(29, 29),
+            intArrayOf(21, 21)
+        )
+    private val REVERSE_DIFFERENCE_WITHOUT2 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(19, 0),
+            intArrayOf(0, 19),
+            intArrayOf(19, 19),
+            intArrayOf(2, 2),
+            intArrayOf(11, 11)
+        )
+
+    private val REVERSE_DIFFERENCE_WITH3 =
+        arrayOf(
+            intArrayOf(40, 40),
+            intArrayOf(40, 59),
+            intArrayOf(59, 40),
+            intArrayOf(59, 59),
+            intArrayOf(41, 41)
+        )
+    private val REVERSE_DIFFERENCE_WITHOUT3 =
+        arrayOf(
+            intArrayOf(0, 0),
+            intArrayOf(19, 0),
+            intArrayOf(0, 19),
+            intArrayOf(19, 19),
+            intArrayOf(20, 20),
+            intArrayOf(39, 39),
+            intArrayOf(2, 2)
+        )
+
+    private var mRegion: Region = Region()
+
+    private fun verifyPointsInsideRegion(area: Array<IntArray>) {
+        for (i in area.indices) {
+            assertTrue(mRegion.contains(area[i][0], area[i][1]))
+        }
+    }
+
+    private fun verifyPointsOutsideRegion(area: Array<IntArray>) {
+        for (i in area.indices) {
+            assertFalse(mRegion.contains(area[i][0], area[i][1]))
+        }
+    }
+
+    @Before
+    fun setup() {
+        mRegion = Region()
+    }
+
+    @Test
+    fun testConstructor() {
+        // Test Region()
+        Region()
+
+        // Test Region(Region)
+        val oriRegion = Region()
+        Region.from(oriRegion)
+
+        // Test Region(Rect)
+        val rect = Rect.from(0, 0, 0, 0)
+        Region.from(rect)
+
+        // Test Region(int, int, int, int)
+        Region.from(0, 0, 100, 100)
+    }
+
+    @Test
+    fun testSet1() {
+        val rect = Rect.from(1, 2, 3, 4)
+        val oriRegion = Region.from(rect)
+        assertTrue(mRegion.set(oriRegion))
+        assertEquals(1, mRegion.bounds.left)
+        assertEquals(2, mRegion.bounds.top)
+        assertEquals(3, mRegion.bounds.right)
+        assertEquals(4, mRegion.bounds.bottom)
+    }
+
+    @Test
+    fun testSet2() {
+        val rect = Rect.from(1, 2, 3, 4)
+        assertTrue(mRegion.set(rect))
+        assertEquals(1, mRegion.bounds.left)
+        assertEquals(2, mRegion.bounds.top)
+        assertEquals(3, mRegion.bounds.right)
+        assertEquals(4, mRegion.bounds.bottom)
+    }
+
+    @Test
+    fun testSet3() {
+        assertTrue(mRegion.set(1, 2, 3, 4))
+        assertEquals(1, mRegion.bounds.left)
+        assertEquals(2, mRegion.bounds.top)
+        assertEquals(3, mRegion.bounds.right)
+        assertEquals(4, mRegion.bounds.bottom)
+    }
+
+    @Test
+    fun testIsRect() {
+        assertFalse(mRegion.isRect())
+        mRegion = Region.from(1, 2, 3, 4)
+        assertTrue(mRegion.isRect())
+    }
+
+    @Test
+    fun testIsComplex() {
+        // Region is empty
+        assertFalse(mRegion.isComplex())
+
+        // Only one rectangle
+        mRegion = Region()
+        mRegion.set(1, 2, 3, 4)
+        assertFalse(mRegion.isComplex())
+
+        // More than one rectangle
+        mRegion = Region()
+        mRegion.set(1, 1, 2, 2)
+        mRegion.union(Rect.from(3, 3, 5, 5))
+        assertTrue(mRegion.isComplex())
+    }
+
+    @Test
+    fun testUnion() {
+        val rect1 = Rect.from(0, 0, 0, 0)
+        val rect2 = Rect.from(0, 0, 20, 20)
+        val rect3 = Rect.from(5, 5, 10, 10)
+        val rect4 = Rect.from(10, 10, 30, 30)
+        val rect5 = Rect.from(40, 40, 60, 60)
+
+        // union (inclusive-or) the two regions
+        mRegion.set(rect2)
+        // union null rectangle
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.union(rect1))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.union(rect3))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.union(rect4))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.union(rect5))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    @Test
+    fun testContains() {
+        mRegion.set(2, 2, 5, 5)
+        // Not contain (1, 1).
+        assertFalse(mRegion.contains(1, 1))
+
+        // Test point inside this region.
+        assertTrue(mRegion.contains(3, 3))
+
+        // Test left-top corner.
+        assertTrue(mRegion.contains(2, 2))
+
+        // Test left-bottom corner.
+        assertTrue(mRegion.contains(2, 4))
+
+        // Test right-top corner.
+        assertTrue(mRegion.contains(4, 2))
+
+        // Test right-bottom corner.
+        assertTrue(mRegion.contains(4, 4))
+
+        // Though you set 5, but 5 is not contained by this region.
+        assertFalse(mRegion.contains(5, 5))
+        assertFalse(mRegion.contains(2, 5))
+        assertFalse(mRegion.contains(5, 2))
+
+        // Set a new rectangle.
+        mRegion.set(6, 6, 8, 8)
+        assertFalse(mRegion.contains(3, 3))
+        assertTrue(mRegion.contains(7, 7))
+    }
+
+    @Test
+    fun testEmpty() {
+        assertTrue(mRegion.isEmpty)
+        mRegion = Region.from(1, 2, 3, 4)
+        assertFalse(mRegion.isEmpty)
+        mRegion.setEmpty()
+        assertTrue(mRegion.isEmpty)
+    }
+
+    @Test
+    fun testOp1() {
+        val rect1 = Rect.from(0, 0, 0, 0)
+        val rect2 = Rect.from(0, 0, 20, 20)
+        val rect3 = Rect.from(5, 5, 10, 10)
+        val rect4 = Rect.from(10, 10, 30, 30)
+        val rect5 = Rect.from(40, 40, 60, 60)
+        verifyNullRegionOp1(rect1)
+        verifyDifferenceOp1(rect1, rect2, rect3, rect4, rect5)
+        verifyIntersectOp1(rect1, rect2, rect3, rect4, rect5)
+        verifyUnionOp1(rect1, rect2, rect3, rect4, rect5)
+        verifyXorOp1(rect1, rect2, rect3, rect4, rect5)
+        verifyReverseDifferenceOp1(rect1, rect2, rect3, rect4, rect5)
+        verifyReplaceOp1(rect1, rect2, rect3, rect4, rect5)
+    }
+
+    private fun verifyNullRegionOp1(rect1: Rect) {
+        // Region without rectangle
+        mRegion = Region()
+        assertFalse(mRegion.op(rect1, Region.Op.DIFFERENCE))
+        assertFalse(mRegion.op(rect1, Region.Op.INTERSECT))
+        assertFalse(mRegion.op(rect1, Region.Op.UNION))
+        assertFalse(mRegion.op(rect1, Region.Op.XOR))
+        assertFalse(mRegion.op(rect1, Region.Op.REVERSE_DIFFERENCE))
+        assertFalse(mRegion.op(rect1, Region.Op.REPLACE))
+    }
+
+    private fun verifyDifferenceOp1(
+        rect1: Rect,
+        rect2: Rect,
+        rect3: Rect,
+        rect4: Rect,
+        rect5: Rect
+    ) {
+        // DIFFERENCE, Region with rectangle
+        // subtract the op region from the first region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(rect2)
+        assertTrue(mRegion.op(rect1, Region.Op.DIFFERENCE))
+
+        // 1. subtract rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(rect3, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH1)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
+
+        // 2. subtract rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(11, 11))
+        assertTrue(mRegion.op(rect4, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
+
+        // 3. subtract rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.op(rect5, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyIntersectOp1(
+        rect1: Rect,
+        rect2: Rect,
+        rect3: Rect,
+        rect4: Rect,
+        rect5: Rect
+    ) {
+        // INTERSECT, Region with rectangle
+        // intersect the two regions
+        mRegion = Region()
+        // intersect null rectangle
+        mRegion.set(rect2)
+        assertFalse(mRegion.op(rect1, Region.Op.INTERSECT))
+
+        // 1. intersect rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.op(rect3, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH1)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
+
+        // 2. intersect rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(9, 9))
+        assertTrue(mRegion.op(rect4, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH2)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
+
+        // 3. intersect rectangle out of this region
+        mRegion.set(rect2)
+        assertFalse(mRegion.op(rect5, Region.Op.INTERSECT))
+    }
+
+    private fun verifyUnionOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
+        // UNION, Region with rectangle
+        // union (inclusive-or) the two regions
+        mRegion = Region()
+        mRegion.set(rect2)
+        // union null rectangle
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(rect1, Region.Op.UNION))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(rect3, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(rect4, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(rect5, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    private fun verifyXorOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
+        // XOR, Region with rectangle
+        // exclusive-or the two regions
+        mRegion = Region()
+        // xor null rectangle
+        mRegion.set(rect2)
+        assertTrue(mRegion.op(rect1, Region.Op.XOR))
+
+        // 1. xor rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(rect3, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH1)
+        verifyPointsOutsideRegion(XOR_WITHOUT1)
+
+        // 2. xor rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(rect4, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH2)
+        verifyPointsOutsideRegion(XOR_WITHOUT2)
+
+        // 3. xor rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(rect5, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH3)
+        verifyPointsOutsideRegion(XOR_WITHOUT3)
+    }
+
+    private fun verifyReverseDifferenceOp1(
+        rect1: Rect,
+        rect2: Rect,
+        rect3: Rect,
+        rect4: Rect,
+        rect5: Rect
+    ) {
+        // REVERSE_DIFFERENCE, Region with rectangle
+        // reverse difference the first region from the op region
+        mRegion = Region()
+        mRegion.set(rect2)
+        // reverse difference null rectangle
+        assertFalse(mRegion.op(rect1, Region.Op.REVERSE_DIFFERENCE))
+
+        // 1. reverse difference rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertFalse(mRegion.op(rect3, Region.Op.REVERSE_DIFFERENCE))
+
+        // 2. reverse difference rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(rect4, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
+
+        // 3. reverse difference rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(rect5, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyReplaceOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
+        // REPLACE, Region with rectangle
+        // replace the dst region with the op region
+        mRegion = Region()
+        mRegion.set(rect2)
+        // subtract null rectangle
+        assertFalse(mRegion.op(rect1, Region.Op.REPLACE))
+        // subtract rectangle inside this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(rect3, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect3, mRegion.bounds)
+        // subtract rectangle overlap this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(rect4, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect4, mRegion.bounds)
+        // subtract rectangle out of this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(rect5, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect5, mRegion.bounds)
+    }
+
+    @Test
+    fun testOp2() {
+        val rect2 = Rect.from(0, 0, 20, 20)
+        val rect3 = Rect.from(5, 5, 10, 10)
+        val rect4 = Rect.from(10, 10, 30, 30)
+        val rect5 = Rect.from(40, 40, 60, 60)
+        verifyNullRegionOp2()
+        verifyDifferenceOp2(rect2)
+        verifyIntersectOp2(rect2)
+        verifyUnionOp2(rect2)
+        verifyXorOp2(rect2)
+        verifyReverseDifferenceOp2(rect2)
+        verifyReplaceOp2(rect2, rect3, rect4, rect5)
+    }
+
+    private fun verifyNullRegionOp2() {
+        // Region without rectangle
+        mRegion = Region()
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.DIFFERENCE))
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.INTERSECT))
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.UNION))
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.XOR))
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REVERSE_DIFFERENCE))
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REPLACE))
+    }
+
+    private fun verifyDifferenceOp2(rect2: Rect) {
+        // DIFFERENCE, Region with rectangle
+        // subtract the op region from the first region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(rect2)
+        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.DIFFERENCE))
+
+        // 1. subtract rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH1)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
+
+        // 2. subtract rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(11, 11))
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
+
+        // 3. subtract rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyIntersectOp2(rect2: Rect) {
+        // INTERSECT, Region with rectangle
+        // intersect the two regions
+        mRegion = Region()
+        // intersect null rectangle
+        mRegion.set(rect2)
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.INTERSECT))
+
+        // 1. intersect rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH1)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
+
+        // 2. intersect rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(9, 9))
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH2)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
+
+        // 3. intersect rectangle out of this region
+        mRegion.set(rect2)
+        assertFalse(mRegion.op(40, 40, 60, 60, Region.Op.INTERSECT))
+    }
+
+    private fun verifyUnionOp2(rect2: Rect) {
+        // UNION, Region with rectangle
+        // union (inclusive-or) the two regions
+        mRegion = Region()
+        mRegion.set(rect2)
+        // union null rectangle
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.UNION))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    private fun verifyXorOp2(rect2: Rect) {
+        // XOR, Region with rectangle
+        // exclusive-or the two regions
+        mRegion = Region()
+        mRegion.set(rect2)
+        // xor null rectangle
+        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.XOR))
+
+        // 1. xor rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH1)
+        verifyPointsOutsideRegion(XOR_WITHOUT1)
+
+        // 2. xor rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH2)
+        verifyPointsOutsideRegion(XOR_WITHOUT2)
+
+        // 3. xor rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH3)
+        verifyPointsOutsideRegion(XOR_WITHOUT3)
+    }
+
+    private fun verifyReverseDifferenceOp2(rect2: Rect) {
+        // REVERSE_DIFFERENCE, Region with rectangle
+        // reverse difference the first region from the op region
+        mRegion = Region()
+        mRegion.set(rect2)
+        // reverse difference null rectangle
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REVERSE_DIFFERENCE))
+        // reverse difference rectangle inside this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertFalse(mRegion.op(5, 5, 10, 10, Region.Op.REVERSE_DIFFERENCE))
+        // reverse difference rectangle overlap this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
+        // reverse difference rectangle out of this region
+        mRegion.set(rect2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyReplaceOp2(rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
+        // REPLACE, Region w1ith rectangle
+        // replace the dst region with the op region
+        mRegion = Region()
+        mRegion.set(rect2)
+        // subtract null rectangle
+        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REPLACE))
+        // subtract rectangle inside this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect3, mRegion.bounds)
+        // subtract rectangle overlap this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect4, mRegion.bounds)
+        // subtract rectangle out of this region
+        mRegion.set(rect2)
+        assertEquals(rect2, mRegion.bounds)
+        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.REPLACE))
+        assertNotSame(rect2, mRegion.bounds)
+        assertEquals(rect5, mRegion.bounds)
+    }
+
+    @Test
+    fun testOp3() {
+        val region1 = Region()
+        val region2 = Region.from(0, 0, 20, 20)
+        val region3 = Region.from(5, 5, 10, 10)
+        val region4 = Region.from(10, 10, 30, 30)
+        val region5 = Region.from(40, 40, 60, 60)
+        verifyNullRegionOp3(region1)
+        verifyDifferenceOp3(region1, region2, region3, region4, region5)
+        verifyIntersectOp3(region1, region2, region3, region4, region5)
+        verifyUnionOp3(region1, region2, region3, region4, region5)
+        verifyXorOp3(region1, region2, region3, region4, region5)
+        verifyReverseDifferenceOp3(region1, region2, region3, region4, region5)
+        verifyReplaceOp3(region1, region2, region3, region4, region5)
+    }
+
+    private fun verifyNullRegionOp3(region1: Region) {
+        // Region without rectangle
+        mRegion = Region()
+        assertFalse(mRegion.op(region1, Region.Op.DIFFERENCE))
+        assertFalse(mRegion.op(region1, Region.Op.INTERSECT))
+        assertFalse(mRegion.op(region1, Region.Op.UNION))
+        assertFalse(mRegion.op(region1, Region.Op.XOR))
+        assertFalse(mRegion.op(region1, Region.Op.REVERSE_DIFFERENCE))
+        assertFalse(mRegion.op(region1, Region.Op.REPLACE))
+    }
+
+    private fun verifyDifferenceOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // DIFFERENCE, Region with rectangle
+        // subtract the op region from the first region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(region2)
+        assertTrue(mRegion.op(region1, Region.Op.DIFFERENCE))
+
+        // 1. subtract rectangle inside this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(region3, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH1)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
+
+        // 2. subtract rectangle overlap this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(11, 11))
+        assertTrue(mRegion.op(region4, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
+
+        // 3. subtract rectangle out of this region
+        mRegion.set(region2)
+        assertTrue(mRegion.op(region5, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyIntersectOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // INTERSECT, Region with rectangle
+        // intersect the two regions
+        mRegion = Region()
+        mRegion.set(region2)
+        // intersect null rectangle
+        assertFalse(mRegion.op(region1, Region.Op.INTERSECT))
+
+        // 1. intersect rectangle inside this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.op(region3, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH1)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
+
+        // 2. intersect rectangle overlap this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(9, 9))
+        assertTrue(mRegion.op(region4, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH2)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
+
+        // 3. intersect rectangle out of this region
+        mRegion.set(region2)
+        assertFalse(mRegion.op(region5, Region.Op.INTERSECT))
+    }
+
+    private fun verifyUnionOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // UNION, Region with rectangle
+        // union (inclusive-or) the two regions
+        mRegion = Region()
+        // union null rectangle
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(region1, Region.Op.UNION))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(region3, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(region4, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(region5, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    private fun verifyXorOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // XOR, Region with rectangle
+        // exclusive-or the two regions
+        mRegion = Region()
+        // xor null rectangle
+        mRegion.set(region2)
+        assertTrue(mRegion.op(region1, Region.Op.XOR))
+
+        // 1. xor rectangle inside this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertTrue(mRegion.op(region3, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH1)
+        verifyPointsOutsideRegion(XOR_WITHOUT1)
+
+        // 2. xor rectangle overlap this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(region4, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH2)
+        verifyPointsOutsideRegion(XOR_WITHOUT2)
+
+        // 3. xor rectangle out of this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(region5, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH3)
+        verifyPointsOutsideRegion(XOR_WITHOUT3)
+    }
+
+    private fun verifyReverseDifferenceOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REVERSE_DIFFERENCE, Region with rectangle
+        // reverse difference the first region from the op region
+        mRegion = Region()
+        // reverse difference null rectangle
+        mRegion.set(region2)
+        assertFalse(mRegion.op(region1, Region.Op.REVERSE_DIFFERENCE))
+
+        // 1. reverse difference rectangle inside this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(6, 6))
+        assertFalse(mRegion.op(region3, Region.Op.REVERSE_DIFFERENCE))
+
+        // 2. reverse difference rectangle overlap this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertTrue(mRegion.contains(11, 11))
+        assertFalse(mRegion.contains(21, 21))
+        assertTrue(mRegion.op(region4, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
+
+        // 3. reverse difference rectangle out of this region
+        mRegion.set(region2)
+        assertTrue(mRegion.contains(2, 2))
+        assertFalse(mRegion.contains(41, 41))
+        assertTrue(mRegion.op(region5, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyReplaceOp3(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REPLACE, Region with rectangle
+        // replace the dst region with the op region
+        mRegion = Region()
+        mRegion.set(region2)
+        // subtract null rectangle
+        assertFalse(mRegion.op(region1, Region.Op.REPLACE))
+        // subtract rectangle inside this region
+        mRegion.set(region2)
+        assertEquals(region2.bounds, mRegion.bounds)
+        assertTrue(mRegion.op(region3, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region3.bounds, mRegion.bounds)
+        // subtract rectangle overlap this region
+        mRegion.set(region2)
+        assertEquals(region2.bounds, mRegion.bounds)
+        assertTrue(mRegion.op(region4, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region4.bounds, mRegion.bounds)
+        // subtract rectangle out of this region
+        mRegion.set(region2)
+        assertEquals(region2.bounds, mRegion.bounds)
+        assertTrue(mRegion.op(region5, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region5.bounds, mRegion.bounds)
+    }
+
+    @Test
+    fun testOp4() {
+        val rect1 = Rect.from(0, 0, 0, 0)
+        val rect2 = Rect.from(0, 0, 20, 20)
+        val region1 = Region()
+        val region2 = Region.from(0, 0, 20, 20)
+        val region3 = Region.from(5, 5, 10, 10)
+        val region4 = Region.from(10, 10, 30, 30)
+        val region5 = Region.from(40, 40, 60, 60)
+        verifyNullRegionOp4(rect1, region1)
+        verifyDifferenceOp4(rect1, rect2, region1, region3, region4, region5)
+        verifyIntersectOp4(rect1, rect2, region1, region3, region4, region5)
+        verifyUnionOp4(rect1, rect2, region1, region3, region4, region5)
+        verifyXorOp4(rect1, rect2, region1, region3, region4, region5)
+        verifyReverseDifferenceOp4(rect1, rect2, region1, region3, region4, region5)
+        verifyReplaceOp4(rect1, rect2, region1, region2, region3, region4, region5)
+    }
+
+    private fun verifyNullRegionOp4(rect1: Rect, region1: Region) {
+        // Region without rectangle
+        mRegion = Region()
+        assertFalse(mRegion.op(rect1, region1, Region.Op.DIFFERENCE))
+        assertFalse(mRegion.op(rect1, region1, Region.Op.INTERSECT))
+        assertFalse(mRegion.op(rect1, region1, Region.Op.UNION))
+        assertFalse(mRegion.op(rect1, region1, Region.Op.XOR))
+        assertFalse(mRegion.op(rect1, region1, Region.Op.REVERSE_DIFFERENCE))
+        assertFalse(mRegion.op(rect1, region1, Region.Op.REPLACE))
+    }
+
+    private fun verifyDifferenceOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // DIFFERENCE, Region with rectangle
+        // subtract the op region from the first region
+        mRegion = Region()
+        // subtract null rectangle
+        assertTrue(mRegion.op(rect2, region1, Region.Op.DIFFERENCE))
+
+        // 1. subtract rectangle inside this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region3, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH1)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
+
+        // 2. subtract rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
+
+        // 3. subtract rectangle out of this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region5, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyIntersectOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // INTERSECT, Region with rectangle
+        // intersect the two regions
+        mRegion = Region()
+        // intersect null rectangle
+        mRegion.set(rect1)
+        assertFalse(mRegion.op(rect2, region1, Region.Op.INTERSECT))
+
+        // 1. intersect rectangle inside this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region3, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH1)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
+
+        // 2. intersect rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH2)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
+
+        // 3. intersect rectangle out of this region
+        mRegion.set(rect1)
+        assertFalse(mRegion.op(rect2, region5, Region.Op.INTERSECT))
+    }
+
+    private fun verifyUnionOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // UNION, Region with rectangle
+        // union (inclusive-or) the two regions
+        mRegion = Region()
+        // union null rectangle
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region1, Region.Op.UNION))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region3, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region5, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    private fun verifyXorOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // XOR, Region with rectangle
+        // exclusive-or the two regions
+        mRegion = Region()
+        // xor null rectangle
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region1, Region.Op.XOR))
+
+        // 1. xor rectangle inside this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region3, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH1)
+        verifyPointsOutsideRegion(XOR_WITHOUT1)
+
+        // 2. xor rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH2)
+        verifyPointsOutsideRegion(XOR_WITHOUT2)
+
+        // 3. xor rectangle out of this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region5, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH3)
+        verifyPointsOutsideRegion(XOR_WITHOUT3)
+    }
+
+    private fun verifyReverseDifferenceOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REVERSE_DIFFERENCE, Region with rectangle
+        // reverse difference the first region from the op region
+        mRegion = Region()
+        // reverse difference null rectangle
+        mRegion.set(rect1)
+        assertFalse(mRegion.op(rect2, region1, Region.Op.REVERSE_DIFFERENCE))
+
+        // 1. reverse difference rectangle inside this region
+        mRegion.set(rect1)
+        assertFalse(mRegion.op(rect2, region3, Region.Op.REVERSE_DIFFERENCE))
+
+        // 2. reverse difference rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
+
+        // 3. reverse difference rectangle out of this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region5, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyReplaceOp4(
+        rect1: Rect,
+        rect2: Rect,
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REPLACE, Region with rectangle
+        // replace the dst region with the op region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(rect1)
+        assertFalse(mRegion.op(rect2, region1, Region.Op.REPLACE))
+        // subtract rectangle inside this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region3, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region3.bounds, mRegion.bounds)
+        // subtract rectangle overlap this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region4, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region4.bounds, mRegion.bounds)
+        // subtract rectangle out of this region
+        mRegion.set(rect1)
+        assertTrue(mRegion.op(rect2, region5, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region5.bounds, mRegion.bounds)
+    }
+
+    @Test
+    fun testOp5() {
+        val region1 = Region()
+        val region2 = Region.from(0, 0, 20, 20)
+        val region3 = Region.from(5, 5, 10, 10)
+        val region4 = Region.from(10, 10, 30, 30)
+        val region5 = Region.from(40, 40, 60, 60)
+        verifyNullRegionOp5(region1)
+        verifyDifferenceOp5(region1, region2, region3, region4, region5)
+        verifyIntersectOp5(region1, region2, region3, region4, region5)
+        verifyUnionOp5(region1, region2, region3, region4, region5)
+        verifyXorOp5(region1, region2, region3, region4, region5)
+        verifyReverseDifferenceOp5(region1, region2, region3, region4, region5)
+        verifyReplaceOp5(region1, region2, region3, region4, region5)
+    }
+
+    private fun verifyNullRegionOp5(region1: Region) {
+        // Region without rectangle
+        mRegion = Region()
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.DIFFERENCE))
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.INTERSECT))
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.UNION))
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.XOR))
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.REVERSE_DIFFERENCE))
+        assertFalse(mRegion.op(mRegion, region1, Region.Op.REPLACE))
+    }
+
+    private fun verifyDifferenceOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // DIFFERENCE, Region with rectangle
+        // subtract the op region from the first region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region1, Region.Op.DIFFERENCE))
+
+        // 1. subtract rectangle inside this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region3, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH1)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
+
+        // 2. subtract rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
+
+        // 3. subtract rectangle out of this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region5, Region.Op.DIFFERENCE))
+        verifyPointsInsideRegion(DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyIntersectOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // INTERSECT, Region with rectangle
+        // intersect the two regions
+        mRegion = Region()
+        // intersect null rectangle
+        mRegion.set(region1)
+        assertFalse(mRegion.op(region2, region1, Region.Op.INTERSECT))
+
+        // 1. intersect rectangle inside this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region3, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH1)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
+
+        // 2. intersect rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.INTERSECT))
+        verifyPointsInsideRegion(INTERSECT_WITH2)
+        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
+
+        // 3. intersect rectangle out of this region
+        mRegion.set(region1)
+        assertFalse(mRegion.op(region2, region5, Region.Op.INTERSECT))
+    }
+
+    private fun verifyUnionOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // UNION, Region with rectangle
+        // union (inclusive-or) the two regions
+        mRegion = Region()
+        // union null rectangle
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region1, Region.Op.UNION))
+        assertTrue(mRegion.contains(6, 6))
+
+        // 1. union rectangle inside this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region3, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH1)
+        verifyPointsOutsideRegion(UNION_WITHOUT1)
+
+        // 2. union rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH2)
+        verifyPointsOutsideRegion(UNION_WITHOUT2)
+
+        // 3. union rectangle out of this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region5, Region.Op.UNION))
+        verifyPointsInsideRegion(UNION_WITH3)
+        verifyPointsOutsideRegion(UNION_WITHOUT3)
+    }
+
+    private fun verifyXorOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // XOR, Region with rectangle
+        // exclusive-or the two regions
+        mRegion = Region()
+        // xor null rectangle
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region1, Region.Op.XOR))
+
+        // 1. xor rectangle inside this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region3, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH1)
+        verifyPointsOutsideRegion(XOR_WITHOUT1)
+
+        // 2. xor rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH2)
+        verifyPointsOutsideRegion(XOR_WITHOUT2)
+
+        // 3. xor rectangle out of this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region5, Region.Op.XOR))
+        verifyPointsInsideRegion(XOR_WITH3)
+        verifyPointsOutsideRegion(XOR_WITHOUT3)
+    }
+
+    private fun verifyReverseDifferenceOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REVERSE_DIFFERENCE, Region with rectangle
+        // reverse difference the first region from the op region
+        mRegion = Region()
+        // reverse difference null rectangle
+        mRegion.set(region1)
+        assertFalse(mRegion.op(region2, region1, Region.Op.REVERSE_DIFFERENCE))
+
+        // 1. reverse difference rectangle inside this region
+        mRegion.set(region1)
+        assertFalse(mRegion.op(region2, region3, Region.Op.REVERSE_DIFFERENCE))
+
+        // 2. reverse difference rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
+
+        // 3. reverse difference rectangle out of this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region5, Region.Op.REVERSE_DIFFERENCE))
+        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
+        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
+    }
+
+    private fun verifyReplaceOp5(
+        region1: Region,
+        region2: Region,
+        region3: Region,
+        region4: Region,
+        region5: Region
+    ) {
+        // REPLACE, Region with rectangle
+        // replace the dst region with the op region
+        mRegion = Region()
+        // subtract null rectangle
+        mRegion.set(region1)
+        assertFalse(mRegion.op(region2, region1, Region.Op.REPLACE))
+        // subtract rectangle inside this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region3, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region3.bounds, mRegion.bounds)
+        // subtract rectangle overlap this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region4, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region4.bounds, mRegion.bounds)
+        // subtract rectangle out of this region
+        mRegion.set(region1)
+        assertTrue(mRegion.op(region2, region5, Region.Op.REPLACE))
+        assertNotSame(region2.bounds, mRegion.bounds)
+        assertEquals(region5.bounds, mRegion.bounds)
+    }
+
+    val flickerRegionOperations =
+        listOf(
+            Region.Op.DIFFERENCE,
+            Region.Op.INTERSECT,
+            Region.Op.UNION,
+            Region.Op.XOR,
+            Region.Op.REVERSE_DIFFERENCE,
+            Region.Op.REPLACE
+        )
+    val nativeRegionOperations =
+        listOf(
+            android.graphics.Region.Op.DIFFERENCE,
+            android.graphics.Region.Op.INTERSECT,
+            android.graphics.Region.Op.UNION,
+            android.graphics.Region.Op.XOR,
+            android.graphics.Region.Op.REVERSE_DIFFERENCE,
+            android.graphics.Region.Op.REPLACE
+        )
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForSingleOperation() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(1)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForSingleOperationFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(1, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForTwoOperations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(2)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForTwoOperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(2, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForThreeOperations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(3)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForThreeOperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(3, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForFourOperations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(4)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForFourOperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(4, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForFiveOperations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(5)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputForFiveOperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(5, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputFor100Operations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(100, iterations = 10)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputFor100OperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(100, startEmpty = true, iterations = 10)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameOutputFor1000Operations() {
+        testFlickerRegionAndNativeRegionProvideSameOutput(100, iterations = 5)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameBoundsForSingleOperation() {
+        testFlickerRegionAndNativeRegionProvideSameBounds(1)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameBoundsForSingleOperationFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameBounds(1, startEmpty = true)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameBoundsForFiveOperations() {
+        testFlickerRegionAndNativeRegionProvideSameBounds(5)
+    }
+
+    @Test
+    fun testFlickerRegionAndNativeRegionProvideSameBoundsForFiveOperationsFromEmpty() {
+        testFlickerRegionAndNativeRegionProvideSameBounds(5, startEmpty = true)
+    }
+
+    private fun testFlickerRegionAgainstNativeRegion(
+        totalOperations: Int,
+        startEmpty: Boolean = false,
+        iterations: Int = 100,
+        assertion:
+            (
+                seed: Long,
+                history: String,
+                flickerRegion: Region,
+                nativeRegion: android.graphics.Region
+            ) -> Any,
+        seed: Long = System.currentTimeMillis()
+    ) {
+        val random = Random(seed)
+
+        for (i in 1..iterations) {
+            val flickerRegion: Region
+            val nativeRegion: android.graphics.Region
+            if (startEmpty) {
+                flickerRegion = Region.EMPTY
+                nativeRegion = android.graphics.Region()
+            } else {
+                val left = random.nextInt(0, 5000)
+                val top = random.nextInt(0, 5000)
+                val right = random.nextInt(left, 5000)
+                val bottom = random.nextInt(top, 5000)
+                flickerRegion = Region.from(left, top, right, bottom)
+                nativeRegion = android.graphics.Region(left, top, right, bottom)
+            }
+            var history = "$flickerRegion\n"
+            for (j in 1..totalOperations) {
+                val left = random.nextInt(0, 5000)
+                val top = random.nextInt(0, 5000)
+                val right = random.nextInt(left, 5000)
+                val bottom = random.nextInt(top, 5000)
+                val otherFlickerRegion = Region.from(left, top, right, bottom)
+                val otherNativeRegion = android.graphics.Region(left, top, right, bottom)
+
+                val operationIndex = random.nextInt(0, flickerRegionOperations.size)
+                val flickerOperation = flickerRegionOperations[operationIndex]
+                val nativeOperation = nativeRegionOperations[operationIndex]
+
+                history += "\t$flickerOperation $otherFlickerRegion\n"
+
+                flickerRegion.op(otherFlickerRegion, flickerOperation)
+                nativeRegion.op(otherNativeRegion, nativeOperation)
+
+                assertion(seed, history, flickerRegion, nativeRegion)
+            }
+        }
+    }
+
+    private fun testFlickerRegionAndNativeRegionProvideSameOutput(
+        totalOperations: Int,
+        startEmpty: Boolean = false,
+        iterations: Int = 100
+    ) {
+        testFlickerRegionAgainstNativeRegion(
+            totalOperations,
+            startEmpty,
+            iterations,
+            {
+                seed: Long,
+                history: String,
+                flickerRegion: Region,
+                nativeRegion: android.graphics.Region ->
+                {
+                    assertEquals(
+                        "Ran with seed $seed\n" +
+                            "$history should equal \n" +
+                            "$nativeRegion\n but was\n$flickerRegion\n\n",
+                        nativeRegion.toString(),
+                        flickerRegion.toString()
+                    )
+                }
+            }
+        )
+    }
+
+    private fun testFlickerRegionAndNativeRegionProvideSameBounds(
+        totalOperations: Int,
+        startEmpty: Boolean = false,
+        iterations: Int = 100
+    ) {
+        testFlickerRegionAgainstNativeRegion(
+            totalOperations,
+            startEmpty,
+            iterations,
+            {
+                seed: Long,
+                history: String,
+                flickerRegion: Region,
+                nativeRegion: android.graphics.Region ->
+                {
+                    val bounds = flickerRegion.bounds
+                    val nBounds = nativeRegion.bounds
+                    assertEquals(
+                        "Ran with seed $seed\n" +
+                            "$history.bounds() should equal \n" +
+                            "${nBounds}\n but was\n${bounds}\n\n",
+                        "${nBounds.left},${nBounds.top},${nBounds.right},${nBounds.bottom}",
+                        "${bounds.left},${bounds.top},${bounds.right},${bounds.bottom}"
+                    )
+                }
+            }
+        )
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTest.kt b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTest.kt
new file mode 100644
index 0000000..353b361
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.common.Cache
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [Layer] tests. To run this test: `atest FlickerLibTest:LayerTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayerTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun hasVerboseFlagsProperty() {
+        assertThat(makeLayerWithDefaults(0x0).verboseFlags).isEqualTo("")
+
+        assertThat(makeLayerWithDefaults(0x1).verboseFlags).isEqualTo("HIDDEN (0x1)")
+
+        assertThat(makeLayerWithDefaults(0x2).verboseFlags).isEqualTo("OPAQUE (0x2)")
+
+        assertThat(makeLayerWithDefaults(0x40).verboseFlags).isEqualTo("SKIP_SCREENSHOT (0x40)")
+
+        assertThat(makeLayerWithDefaults(0x80).verboseFlags).isEqualTo("SECURE (0x80)")
+
+        assertThat(makeLayerWithDefaults(0x100).verboseFlags)
+            .isEqualTo("ENABLE_BACKPRESSURE (0x100)")
+
+        assertThat(makeLayerWithDefaults(0x200).verboseFlags)
+            .isEqualTo("DISPLAY_DECORATION (0x200)")
+
+        assertThat(makeLayerWithDefaults(0x400).verboseFlags)
+            .isEqualTo("IGNORE_DESTINATION_FRAME (0x400)")
+
+        assertThat(makeLayerWithDefaults(0xc3).verboseFlags)
+            .isEqualTo("HIDDEN|OPAQUE|SKIP_SCREENSHOT|SECURE (0xc3)")
+    }
+
+    @Test
+    fun useVisibleRegionIfCompositionStateIsAvailableForVisibility() {
+        assertThat(
+                makeLayerWithDefaults(
+                        excludeCompositionState = false,
+                        visibleRegion = Region.EMPTY,
+                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
+                    )
+                    .isVisible
+            )
+            .isFalse()
+        assertThat(
+                makeLayerWithDefaults(
+                        excludeCompositionState = false,
+                        visibleRegion = Region.from(0, 0, 100, 100),
+                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
+                    )
+                    .isVisible
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun fallbackOnLayerBoundsIfCompositionStateIsNotAvailableForVisibility() {
+        assertThat(
+                makeLayerWithDefaults(
+                        excludeCompositionState = true,
+                        bounds = RectF.EMPTY,
+                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
+                    )
+                    .isVisible
+            )
+            .isFalse()
+        assertThat(
+                makeLayerWithDefaults(
+                        excludeCompositionState = true,
+                        bounds = RectF.from(0f, 0f, 100f, 100f),
+                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
+                    )
+                    .isVisible
+            )
+            .isTrue()
+        assertThat(
+                makeLayerWithDefaults(
+                        excludeCompositionState = true,
+                        visibleRegion = Region.from(0, 0, 100, 100),
+                        bounds = RectF.EMPTY,
+                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
+                    )
+                    .isVisible
+            )
+            .isFalse()
+    }
+
+    private fun makeLayerWithDefaults(
+        flags: Int = 0x0,
+        excludeCompositionState: Boolean = false,
+        visibleRegion: Region = Region.EMPTY,
+        bounds: RectF = RectF.EMPTY,
+        activeBuffer: ActiveBuffer = ActiveBuffer.EMPTY
+    ): Layer {
+        return Layer.from(
+            "",
+            0,
+            0,
+            0,
+            visibleRegion,
+            activeBuffer,
+            flags,
+            bounds,
+            Color.EMPTY,
+            false,
+            -1f,
+            -1f,
+            "",
+            RectF.EMPTY,
+            Transform.EMPTY,
+            RectF.EMPTY,
+            -1,
+            -1,
+            Transform.EMPTY,
+            HwcCompositionType.INVALID,
+            RectF.EMPTY,
+            Rect.EMPTY,
+            -1,
+            null,
+            false,
+            -1,
+            -1,
+            Transform.EMPTY,
+            Color.EMPTY,
+            RectF.EMPTY,
+            Transform.EMPTY,
+            null,
+            excludeCompositionState
+        )
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilderTest.kt b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilderTest.kt
new file mode 100644
index 0000000..a262fd4
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayerTraceEntryBuilderTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [LayerTraceEntryBuilder] tests. To run this test: `atest
+ * FlickerLibTest:LayerTraceEntryBuilderTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayerTraceEntryBuilderTest {
+
+    @Test
+    fun createsEntryWithCorrectClockTime() {
+        val builder =
+            LayerTraceEntryBuilder()
+                .setElapsedTimestamp("100")
+                .setLayers(emptyArray())
+                .setDisplays(emptyArray())
+                .setVSyncId("123")
+                .setRealToElapsedTimeOffsetNs("500")
+        val entry = builder.build()
+        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
+        Truth.assertThat(entry.clockTimestamp).isEqualTo(600)
+
+        Truth.assertThat(entry.timestamp.elapsedNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
+    }
+
+    @Test
+    fun supportsMissingRealToElapsedTimeOffsetNs() {
+        val builder =
+            LayerTraceEntryBuilder()
+                .setElapsedTimestamp("100")
+                .setLayers(emptyArray())
+                .setDisplays(emptyArray())
+                .setVSyncId("123")
+        val entry = builder.build()
+        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
+        Truth.assertThat(entry.clockTimestamp).isEqualTo(null)
+
+        Truth.assertThat(entry.timestamp.elapsedNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceEntryTest.kt b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceEntryTest.kt
new file mode 100644
index 0000000..90f1885
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceEntryTest.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.TestComponents
+import android.tools.assertThatErrorContainsDebugInfo
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.readLayerTraceFromFile
+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 [LayerTraceEntry] tests. To run this test: `atest FlickerLibTest:LayersTraceTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayersTraceEntryTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val error =
+            assertThrows<AssertionError> {
+                LayersTraceSubject(layersTraceEntries).first().contains(TestComponents.IMAGINARY)
+            }
+        assertThatErrorContainsDebugInfo(error)
+    }
+
+    @Test
+    fun canParseAllLayers() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        Truth.assertThat(trace.entries).isNotEmpty()
+        Truth.assertThat(trace.entries.first().timestamp.systemUptimeNanos).isEqualTo(922839428857)
+        Truth.assertThat(trace.entries.last().timestamp.systemUptimeNanos).isEqualTo(941432656959)
+        Truth.assertThat(trace.entries.last().flattenedLayers).asList().hasSize(57)
+    }
+
+    @Test
+    fun canParseVisibleLayersLauncher() {
+        val trace =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        val visibleLayers =
+            trace
+                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90480846872160))
+                .visibleLayers
+        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
+        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(6)
+        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
+        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
+        Truth.assertThat(msg).contains("NavigationBar0#0")
+        Truth.assertThat(msg).contains("ImageWallpaper#0")
+        Truth.assertThat(msg).contains("StatusBar#0")
+        Truth.assertThat(msg).contains("NexusLauncherActivity#0")
+    }
+
+    @Test
+    fun canParseVisibleLayersSplitScreen() {
+        val trace =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        val visibleLayers =
+            trace
+                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90493757372977))
+                .visibleLayers
+        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
+        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(7)
+        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
+        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
+        Truth.assertThat(msg).contains("NavigationBar0#0")
+        Truth.assertThat(msg).contains("StatusBar#0")
+        Truth.assertThat(msg).contains("DockedStackDivider#0")
+        Truth.assertThat(msg).contains("ConversationListActivity#0")
+        Truth.assertThat(msg).contains("GoogleDialtactsActivity#0")
+    }
+
+    @Test
+    fun canParseVisibleLayersInTransition() {
+        val trace =
+            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
+        val visibleLayers =
+            trace
+                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90488463619533))
+                .visibleLayers
+        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
+        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(10)
+        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
+        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
+        Truth.assertThat(msg).contains("NavigationBar0#0")
+        Truth.assertThat(msg).contains("StatusBar#0")
+        Truth.assertThat(msg).contains("DockedStackDivider#0")
+        Truth.assertThat(msg)
+            .contains("SnapshotStartingWindow for taskId=21 - " + "task-snapshot-surface#0")
+        Truth.assertThat(msg).contains("SnapshotStartingWindow for taskId=21")
+        Truth.assertThat(msg).contains("NexusLauncherActivity#0")
+        Truth.assertThat(msg).contains("ImageWallpaper#0")
+        Truth.assertThat(msg).contains("ConversationListActivity#0")
+    }
+
+    @Test
+    fun canParseLayerHierarchy() {
+        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        Truth.assertThat(trace.entries).isNotEmpty()
+        Truth.assertThat(trace.entries.first().timestamp.systemUptimeNanos).isEqualTo(922839428857)
+        Truth.assertThat(trace.entries.last().timestamp.systemUptimeNanos).isEqualTo(941432656959)
+        Truth.assertThat(trace.entries.first().flattenedLayers).asList().hasSize(57)
+        val layers = trace.entries.first().children
+        Truth.assertThat(layers[0].children).asList().hasSize(3)
+        Truth.assertThat(layers[1].children).isEmpty()
+    }
+
+    // b/76099859
+    @Test
+    fun canDetectOrphanLayers() {
+        try {
+            readLayerTraceFromFile(
+                    "layers_trace_orphanlayers.pb",
+                    ignoreOrphanLayers = false,
+                    legacyTrace = true
+                )
+                .entries
+                .first()
+                .flattenedLayers
+            error("Failed to detect orphaned layers.")
+        } catch (exception: RuntimeException) {
+            Truth.assertThat(exception.message)
+                .contains(
+                    "Failed to parse layers trace. Found orphan layer with id = 49 with" +
+                        " parentId = 1006"
+                )
+        }
+    }
+
+    @Test
+    fun testCanParseNonCroppedLayerWithHWC() {
+        val layerName = "BackColorSurface#0"
+        val layersTrace =
+            readLayerTraceFromFile("layers_trace_backcolorsurface.pb", legacyTrace = true)
+        val entry =
+            layersTrace.getEntryExactlyAt(
+                CrossPlatform.timestamp.from(systemUptimeNanos = 131954021476)
+            )
+        Truth.assertWithMessage("$layerName should not be visible")
+            .that(entry.visibleLayers.map { it.name })
+            .doesNotContain(layerName)
+        val layer = entry.flattenedLayers.first { it.name == layerName }
+        Truth.assertWithMessage("$layerName should be invisible because of HWC region")
+            .that(layer.visibilityReason)
+            .asList()
+            .contains("Visible region calculated by Composition Engine is empty")
+    }
+
+    @Test
+    fun canParseTraceEmptyState() {
+        val layersTrace =
+            readLayerTraceFromFile("layers_trace_empty_state.winscope", legacyTrace = true)
+        val emptyStates = layersTrace.entries.filter { it.flattenedLayers.isEmpty() }
+
+        Truth.assertWithMessage("Some states in the trace should be empty")
+            .that(emptyStates)
+            .isNotEmpty()
+
+        Truth.assertWithMessage("Expected state 4d4h41m14s193ms to be empty")
+            .that(emptyStates.first().timestamp.systemUptimeNanos)
+            .isEqualTo(362474193519965)
+    }
+
+    @Test
+    fun canDetectInvisibleLayerOutOfScreen() {
+        val layersTrace = readLayerTraceFromFile("layers_trace_visible_outside_bounds.winscope")
+        val subject =
+            LayersTraceSubject(layersTrace)
+                .getEntryBySystemUpTime(1253267561044, byElapsedTimestamp = true)
+        val region = subject.visibleRegion(ComponentNameMatcher.IME_SNAPSHOT)
+        region.isEmpty()
+        subject.isInvisible(ComponentNameMatcher.IME_SNAPSHOT)
+    }
+
+    @Test
+    fun canDetectInvisibleLayerOutOfScreen_ConsecutiveLayers() {
+        val layersTrace = readLayerTraceFromFile("layers_trace_visible_outside_bounds.winscope")
+        val subject = LayersTraceSubject(layersTrace)
+        subject.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @Test
+    fun usesRealTimestampWhenAvailableAndFallsbackOnElapsedTimestamp() {
+        var entry =
+            LayerTraceEntry(
+                elapsedTimestamp = 100,
+                clockTimestamp = 600,
+                hwcBlob = "",
+                where = "",
+                displays = emptyArray(),
+                vSyncId = 123,
+                _rootLayers = emptyArray()
+            )
+        Truth.assertThat(entry.timestamp.elapsedNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
+
+        entry =
+            LayerTraceEntry(
+                elapsedTimestamp = 100,
+                clockTimestamp = null,
+                hwcBlob = "",
+                where = "",
+                displays = emptyArray(),
+                vSyncId = 123,
+                _rootLayers = emptyArray()
+            )
+        Truth.assertThat(entry.timestamp.elapsedNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceTest.kt b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceTest.kt
new file mode 100644
index 0000000..464cac6
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/surfaceflinger/LayersTraceTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.traces.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.assertThatErrorContainsDebugInfo
+import android.tools.assertThrows
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.readLayerTraceFromFile
+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 [LayersTrace] tests. To run this test: `atest FlickerLibTest:LayersTraceTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayersTraceTest {
+    private fun detectRootLayer(fileName: String, legacyTrace: Boolean = false) {
+        val layersTrace = readLayerTraceFromFile(fileName, legacyTrace = legacyTrace)
+        for (entry in layersTrace.entries) {
+            val rootLayers = entry.children
+            Truth.assertWithMessage("Does not have any root layer")
+                .that(rootLayers.size)
+                .isGreaterThan(0)
+            val firstParentId = rootLayers.first().parentId
+            Truth.assertWithMessage("Has multiple root layers")
+                .that(rootLayers.all { it.parentId == firstParentId })
+                .isTrue()
+        }
+    }
+
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun testCanDetectRootLayer() {
+        detectRootLayer("layers_trace_root.pb", legacyTrace = true)
+    }
+
+    @Test
+    fun testCanDetectRootLayerAOSP() {
+        detectRootLayer("layers_trace_root_aosp.pb", legacyTrace = true)
+    }
+
+    @Test
+    fun canTestLayerOccludedByAppLayerHasVisibleRegion() {
+        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
+        val entry =
+            trace.getEntryExactlyAt(
+                CrossPlatform.timestamp.from(systemUptimeNanos = 1700382131522L)
+            )
+        val component =
+            ComponentNameMatcher("", "com.android.server.wm.flicker.testapp.SimpleActivity#0")
+        val layer = entry.getLayerWithBuffer(component)
+        Truth.assertWithMessage("App should be visible")
+            .that(layer?.visibleRegion?.isEmpty)
+            .isFalse()
+        Truth.assertWithMessage("App should visible region")
+            .that(layer?.visibleRegion?.toString())
+            .contains("SkRegion((346,1583,1094,2839))")
+
+        val splashScreenComponent =
+            ComponentNameMatcher("", "Splash Screen com.android.server.wm.flicker.testapp#0")
+        val splashScreenLayer = entry.getLayerWithBuffer(splashScreenComponent)
+        Truth.assertWithMessage("Splash screen should be visible")
+            .that(splashScreenLayer?.visibleRegion?.isEmpty)
+            .isFalse()
+        Truth.assertWithMessage("Splash screen visible region")
+            .that(splashScreenLayer?.visibleRegion?.toString())
+            .contains("SkRegion((346,1583,1094,2839))")
+    }
+
+    @Test
+    fun canTestLayerOccludedByAppLayerIsOccludedBySplashScreen() {
+        val layerName = "com.android.server.wm.flicker.testapp.SimpleActivity#0"
+        val component = ComponentNameMatcher("", layerName)
+        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
+        val entry =
+            trace.getEntryExactlyAt(
+                CrossPlatform.timestamp.from(systemUptimeNanos = 1700382131522L)
+            )
+        val layer = entry.getLayerWithBuffer(component)
+        val occludedBy = layer?.occludedBy ?: emptyArray()
+        val partiallyOccludedBy = layer?.partiallyOccludedBy ?: emptyArray()
+        Truth.assertWithMessage("Layer $layerName should be occluded").that(occludedBy).isNotEmpty()
+        Truth.assertWithMessage("Layer $layerName should not be partially occluded")
+            .that(partiallyOccludedBy)
+            .isEmpty()
+        Truth.assertWithMessage("Layer $layerName should be occluded")
+            .that(occludedBy.joinToString())
+            .contains(
+                "Splash Screen com.android.server.wm.flicker.testapp#0 buffer:w:1440, " +
+                    "h:3040, stride:1472, format:1 frame#1 visible:" +
+                    "SkRegion((346,1583,1094,2839))"
+            )
+    }
+
+    @Test
+    fun exceptionContainsDebugInfo() {
+        val layersTraceEntries =
+            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
+        val error =
+            assertThrows<AssertionError> { LayersTraceSubject(layersTraceEntries).isEmpty() }
+        assertThatErrorContainsDebugInfo(error, withBlameEntry = false)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerStateTest.kt b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerStateTest.kt
new file mode 100644
index 0000000..0f5c775
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerStateTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.traces.wm
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [WindowManagerState] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerStateTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerStateTest {
+
+    private val emptyRootContainer =
+        RootWindowContainer(
+            WindowContainer(
+                title = "root",
+                token = "",
+                orientation = 0,
+                layerId = 0,
+                _isVisible = true,
+                children = emptyArray(),
+                configurationContainer = ConfigurationContainer(null, null, null),
+                computedZ = 0
+            )
+        )
+
+    @Test
+    fun usesRealTimestampWhenAvailableAndFallsbackOnElapsedTimestamp() {
+        var entry =
+            WindowManagerState(
+                elapsedTimestamp = 100,
+                clockTimestamp = 600,
+                where = "",
+                policy = null,
+                focusedApp = "",
+                focusedDisplayId = 0,
+                _focusedWindow = "",
+                inputMethodWindowAppToken = "",
+                isHomeRecentsComponent = false,
+                isDisplayFrozen = false,
+                _pendingActivities = emptyArray(),
+                root = emptyRootContainer,
+                keyguardControllerState =
+                    KeyguardControllerState.from(
+                        isAodShowing = false,
+                        isKeyguardShowing = false,
+                        keyguardOccludedStates = mapOf()
+                    )
+            )
+        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
+
+        entry =
+            WindowManagerState(
+                elapsedTimestamp = 100,
+                clockTimestamp = null,
+                where = "",
+                policy = null,
+                focusedApp = "",
+                focusedDisplayId = 0,
+                _focusedWindow = "",
+                inputMethodWindowAppToken = "",
+                isHomeRecentsComponent = false,
+                isDisplayFrozen = false,
+                _pendingActivities = emptyArray(),
+                root = emptyRootContainer,
+                keyguardControllerState =
+                    KeyguardControllerState.from(
+                        isAodShowing = false,
+                        isKeyguardShowing = false,
+                        keyguardOccludedStates = mapOf()
+                    )
+            )
+        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.unixNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilderTest.kt b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilderTest.kt
new file mode 100644
index 0000000..81e6696
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceEntryBuilderTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.traces.wm
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [WindowManagerTraceEntryBuilder] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerTraceEntryBuilderTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerTraceEntryBuilderTest {
+
+    private val emptyRootContainer =
+        RootWindowContainer(
+            WindowContainer(
+                title = "root",
+                token = "",
+                orientation = 0,
+                layerId = 0,
+                _isVisible = true,
+                children = emptyArray(),
+                configurationContainer = ConfigurationContainer(null, null, null),
+                computedZ = 0
+            )
+        )
+
+    @Test
+    fun createsEntryWithCorrectClockTime() {
+        val builder =
+            WindowManagerTraceEntryBuilder(
+                _elapsedTimestamp = "100",
+                where = "",
+                policy = null,
+                focusedApp = "",
+                focusedDisplayId = 0,
+                focusedWindow = "",
+                inputMethodWindowAppToken = "",
+                isHomeRecentsComponent = false,
+                isDisplayFrozen = false,
+                pendingActivities = emptyArray(),
+                root = emptyRootContainer,
+                keyguardControllerState =
+                    KeyguardControllerState.from(
+                        isAodShowing = false,
+                        isKeyguardShowing = false,
+                        keyguardOccludedStates = mapOf()
+                    ),
+                realToElapsedTimeOffsetNs = "500"
+            )
+        val entry = builder.build()
+        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
+        Truth.assertThat(entry.clockTimestamp).isEqualTo(600)
+
+        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().systemUptimeNanos)
+        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
+    }
+
+    @Test
+    fun supportsMissingRealToElapsedTimeOffsetNs() {
+        val builder =
+            WindowManagerTraceEntryBuilder(
+                _elapsedTimestamp = "100",
+                where = "",
+                policy = null,
+                focusedApp = "",
+                focusedDisplayId = 0,
+                focusedWindow = "",
+                inputMethodWindowAppToken = "",
+                isHomeRecentsComponent = false,
+                isDisplayFrozen = false,
+                pendingActivities = emptyArray(),
+                root = emptyRootContainer,
+                keyguardControllerState =
+                    KeyguardControllerState.from(
+                        isAodShowing = false,
+                        isKeyguardShowing = false,
+                        keyguardOccludedStates = mapOf()
+                    )
+            )
+        val entry = builder.build()
+        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
+        Truth.assertThat(entry.clockTimestamp).isEqualTo(null)
+
+        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
+        Truth.assertThat(entry.timestamp.systemUptimeNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().systemUptimeNanos)
+        Truth.assertThat(entry.timestamp.unixNanos)
+            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceTest.kt b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceTest.kt
new file mode 100644
index 0000000..a3b26da
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/common/traces/wm/WindowManagerTraceTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.traces.wm
+
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.readWmTraceFromFile
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.lang.reflect.Modifier
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [WindowManagerTrace] tests. To run this test: `atest
+ * FlickerLibTest:WindowManagerTraceTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerTraceTest {
+    private val trace
+        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
+
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun canDetectAppWindow() {
+        val appWindows =
+            trace
+                .getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9213763541297L))
+                .appWindows
+        assertWithMessage("Unable to detect app windows").that(appWindows.size).isEqualTo(2)
+    }
+
+    /**
+     * Access all public methods and invokes all public getters from the object to check that all
+     * lazy properties contain valid values
+     */
+    private fun <T> Class<T>.accessProperties(obj: Any) {
+        val propertyValues =
+            this.declaredFields
+                .filter { Modifier.isPublic(it.modifiers) }
+                .map { kotlin.runCatching { Pair(it.name, it.get(obj)) } }
+                .filter { it.isFailure }
+
+        assertWithMessage(
+                "The following properties could not be read: " + propertyValues.joinToString("\n")
+            )
+            .that(propertyValues)
+            .isEmpty()
+
+        val getterValues =
+            this.declaredMethods
+                .filter {
+                    Modifier.isPublic(it.modifiers) &&
+                        it.name.startsWith("get") &&
+                        it.parameterCount == 0
+                }
+                .map { kotlin.runCatching { Pair(it.name, it.invoke(obj)) } }
+                .filter { it.isFailure }
+
+        assertWithMessage(
+                "The following methods could not be invoked: " + getterValues.joinToString("\n")
+            )
+            .that(getterValues)
+            .isEmpty()
+
+        this.superclass?.accessProperties(obj)
+        if (obj is WindowContainer) {
+            obj.children.forEach { it::class.java.accessProperties(it) }
+        }
+    }
+
+    /**
+     * Tests if all properties of the flicker objects are accessible. This is necessary because most
+     * values are lazy initialized and only trigger errors when being accessed for the first time.
+     */
+    @Test
+    fun canAccessAllProperties() {
+        arrayOf("wm_trace_activity_transition.pb", "wm_trace_openchrome2.pb").forEach { traceName ->
+            val trace = readWmTraceFromFile(traceName, legacyTrace = true)
+            assertWithMessage("Unable to parse dump").that(trace.entries.size).isGreaterThan(1)
+
+            trace.entries.forEach { entry: WindowManagerState ->
+                entry::class.java.accessProperties(entry)
+                entry.displays.forEach { it::class.java.accessProperties(it) }
+            }
+        }
+    }
+
+    @Test
+    fun canDetectValidState() {
+        val entry =
+            trace.getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9213763541297))
+        assertWithMessage("${entry.timestamp}: ${entry.getIsIncompleteReason()}")
+            .that(entry.isIncomplete())
+            .isFalse()
+    }
+
+    @Test
+    fun canDetectInvalidState() {
+        val entry =
+            trace.getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9215511235586))
+        assertWithMessage("${entry.timestamp}: ${entry.getIsIncompleteReason()}")
+            .that(entry.isIncomplete())
+            .isTrue()
+
+        assertThat(entry.getIsIncompleteReason()).contains("No resumed activities found")
+    }
+
+    @Test
+    fun canSlice() {
+        val trace =
+            readWmTraceFromFile(
+                "wm_trace_openchrome2.pb",
+                from = 174686204723645,
+                to = 174686640998584,
+                legacyTrace = true
+            )
+
+        assertThat(trace.entries).asList().isNotEmpty()
+        assertThat(trace.entries.first().timestamp.elapsedNanos).isEqualTo(174686204723645)
+        assertThat(trace.entries.last().timestamp.elapsedNanos).isEqualTo(174686640998584)
+    }
+
+    @Test
+    fun canSliceWithWrongTimestamps() {
+        val trace =
+            readWmTraceFromFile(
+                "wm_trace_openchrome2.pb",
+                from = 9213763541297,
+                to = 9215895891561,
+                legacyTrace = true
+            )
+        assertThat(trace.entries).asList().isEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/assertions/ArtifactAssertionRunnerTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/assertions/ArtifactAssertionRunnerTest.kt
new file mode 100644
index 0000000..8b037ff
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/assertions/ArtifactAssertionRunnerTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.InitRule
+import android.tools.assertExceptionMessage
+import android.tools.common.Tag
+import android.tools.common.assertions.Consts
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.IResultData
+import android.tools.device.traces.monitors.events.EventLogMonitor
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Tests for [ArtifactAssertionRunner]
+ *
+ * run with `atest FlickerLibTest:ArtifactAssertionRunnerTest`
+ */
+class ArtifactAssertionRunnerTest {
+    private var executionCount = 0
+
+    private val assertionSuccess = newAssertionData { executionCount++ }
+    private val assertionFailure = newAssertionData {
+        executionCount++
+        error(Consts.FAILURE)
+    }
+
+    @Before
+    fun setup() {
+        executionCount = 0
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        outputFileName(RunStatus.ASSERTION_FAILED).deleteIfExists()
+        outputFileName(RunStatus.ASSERTION_SUCCESS).deleteIfExists()
+    }
+
+    @Test
+    fun executes() {
+        val result = newResultReaderWithEmptySubject()
+        val runner = ArtifactAssertionRunner(result)
+        val firstAssertionResult = runner.runAssertion(assertionSuccess)
+        val lastAssertionResult = runner.runAssertion(assertionSuccess)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
+        verifyExceptionMessage(firstAssertionResult, expectSuccess = true)
+        verifyExceptionMessage(lastAssertionResult, expectSuccess = true)
+    }
+
+    @Test
+    fun executesFailure() {
+        val result = newResultReaderWithEmptySubject()
+        val runner = ArtifactAssertionRunner(result)
+        val firstAssertionResult = runner.runAssertion(assertionFailure)
+        val lastAssertionResult = runner.runAssertion(assertionFailure)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+
+        verifyExceptionMessage(firstAssertionResult, expectSuccess = false)
+        verifyExceptionMessage(lastAssertionResult, expectSuccess = false)
+        Truth.assertWithMessage("Same exception")
+            .that(firstAssertionResult)
+            .hasMessageThat()
+            .isEqualTo(lastAssertionResult?.message)
+    }
+
+    @Test
+    fun updatesRunStatusFailureFirst() {
+        val result = newResultReaderWithEmptySubject()
+        val runner = ArtifactAssertionRunner(result)
+        val firstAssertionResult = runner.runAssertion(assertionFailure)
+        val lastAssertionResult = runner.runAssertion(assertionSuccess)
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        verifyExceptionMessage(firstAssertionResult, expectSuccess = false)
+        verifyExceptionMessage(lastAssertionResult, expectSuccess = true)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+    }
+
+    @Test
+    fun updatesRunStatusFailureLast() {
+        val result = newResultReaderWithEmptySubject()
+        val runner = ArtifactAssertionRunner(result)
+        val firstAssertionResult = runner.runAssertion(assertionSuccess)
+        val lastAssertionResult = runner.runAssertion(assertionFailure)
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        verifyExceptionMessage(firstAssertionResult, expectSuccess = true)
+        verifyExceptionMessage(lastAssertionResult, expectSuccess = false)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+    }
+
+    private fun verifyExceptionMessage(actual: Throwable?, expectSuccess: Boolean) {
+        if (expectSuccess) {
+            Truth.assertWithMessage("Expected exception").that(actual).isNull()
+        } else {
+            assertExceptionMessage(actual, Consts.FAILURE)
+        }
+    }
+
+    companion object {
+        private fun newAssertionData(assertion: (FlickerSubject) -> Unit) =
+            AssertionData(Tag.ALL, EventLogSubject::class, assertion)
+
+        private fun newResultReaderWithEmptySubject(): IResultData {
+            val writer = newTestResultWriter()
+            val monitor = EventLogMonitor()
+            monitor.start()
+            monitor.stop(writer)
+            return writer.write()
+        }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionDataFactoryTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionDataFactoryTest.kt
new file mode 100644
index 0000000..8d337b4
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionDataFactoryTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.common.Tag
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+import org.junit.Test
+
+/** Tests for [AssertionDataFactoryTest] */
+class AssertionDataFactoryTest : AssertionStateDataFactoryTest() {
+    override val wmAssertionFactory: AssertionStateDataFactory
+        get() =
+            AssertionDataFactory(WindowManagerStateSubject::class, WindowManagerTraceSubject::class)
+    override val layersAssertionFactory: AssertionStateDataFactory
+        get() = AssertionDataFactory(LayerTraceEntrySubject::class, LayersTraceSubject::class)
+
+    @Test
+    fun checkBuildsTraceAssertion() {
+        validate(
+            (wmAssertionFactory as AssertionDataFactory).createTraceAssertion {},
+            WindowManagerTraceSubject::class,
+            Tag.ALL
+        )
+        validate(
+            (layersAssertionFactory as AssertionDataFactory).createTraceAssertion {},
+            LayersTraceSubject::class,
+            Tag.ALL
+        )
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionStateDataFactoryTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionStateDataFactoryTest.kt
new file mode 100644
index 0000000..8679516
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/assertions/AssertionStateDataFactoryTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.device.flicker.assertions
+
+import android.tools.InitRule
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import com.google.common.truth.Truth
+import kotlin.reflect.KClass
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [AssertionStateDataFactory] */
+open class AssertionStateDataFactoryTest {
+    protected open val wmAssertionFactory
+        get() = AssertionStateDataFactory(WindowManagerStateSubject::class)
+    protected open val layersAssertionFactory
+        get() = AssertionStateDataFactory(LayerTraceEntrySubject::class)
+    protected open val eventLogAssertionFactory
+        get() = AssertionStateDataFactory(EventLogSubject::class)
+
+    @Test
+    open fun checkBuildsStartAssertion() {
+        validate(
+            wmAssertionFactory.createStartStateAssertion {},
+            WindowManagerStateSubject::class,
+            Tag.START
+        )
+        validate(
+            layersAssertionFactory.createStartStateAssertion {},
+            LayerTraceEntrySubject::class,
+            Tag.START
+        )
+        validate(
+            eventLogAssertionFactory.createStartStateAssertion {},
+            EventLogSubject::class,
+            Tag.START
+        )
+    }
+
+    @Test
+    open fun checkBuildsEndAssertion() {
+        validate(
+            wmAssertionFactory.createEndStateAssertion {},
+            WindowManagerStateSubject::class,
+            Tag.END
+        )
+        validate(
+            layersAssertionFactory.createEndStateAssertion {},
+            LayerTraceEntrySubject::class,
+            Tag.END
+        )
+        validate(
+            eventLogAssertionFactory.createEndStateAssertion {},
+            EventLogSubject::class,
+            Tag.END
+        )
+    }
+
+    @Test
+    open fun checkBuildsTagAssertion() {
+        validate(
+            wmAssertionFactory.createTagAssertion(TAG) {},
+            WindowManagerStateSubject::class,
+            TAG
+        )
+        validate(
+            layersAssertionFactory.createTagAssertion(TAG) {},
+            LayerTraceEntrySubject::class,
+            TAG
+        )
+        validate(eventLogAssertionFactory.createTagAssertion(TAG) {}, EventLogSubject::class, TAG)
+    }
+
+    protected fun validate(
+        assertionData: AssertionData,
+        expectedSubject: KClass<*>,
+        expectedTag: String
+    ) {
+        Truth.assertWithMessage("Subject")
+            .that(assertionData.expectedSubjectClass)
+            .isEqualTo(expectedSubject)
+        Truth.assertWithMessage("Tag").that(assertionData.tag).isEqualTo(expectedTag)
+    }
+
+    companion object {
+        internal const val TAG = "tag"
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedAssertionRunnerTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedAssertionRunnerTest.kt
new file mode 100644
index 0000000..0509e9e
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedAssertionRunnerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertExceptionMessage
+import android.tools.common.Tag
+import android.tools.common.flicker.assertions.AssertionData
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.events.EventLogSubject
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.monitors.events.EventLogMonitor
+import android.tools.newTestResultWriter
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [CachedAssertionRunner] */
+@SuppressLint("VisibleForTests")
+class CachedAssertionRunnerTest {
+    private var executionCount = 0
+
+    private val assertionSuccess = newAssertionData { executionCount++ }
+    private val assertionFailure = newAssertionData {
+        executionCount++
+        error(Consts.FAILURE)
+    }
+
+    @Before
+    fun setup() {
+        executionCount = 0
+        DataStore.clear()
+        val writer = newTestResultWriter()
+        val monitor = EventLogMonitor()
+        monitor.start()
+        monitor.stop(writer)
+        val result = writer.write()
+        DataStore.addResult(TEST_SCENARIO, result)
+    }
+
+    @Test
+    fun executes() {
+        val runner = CachedAssertionRunner(TEST_SCENARIO)
+        val firstAssertionResult = runner.runAssertion(assertionSuccess)
+        val lastAssertionResult = runner.runAssertion(assertionSuccess)
+        val result = DataStore.getResult(TEST_SCENARIO)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
+        Truth.assertWithMessage("Expected exception").that(firstAssertionResult).isNull()
+        Truth.assertWithMessage("Expected exception").that(lastAssertionResult).isNull()
+    }
+
+    @Test
+    fun executesFailure() {
+        val runner = CachedAssertionRunner(TEST_SCENARIO)
+        val firstAssertionResult = runner.runAssertion(assertionFailure)
+        val lastAssertionResult = runner.runAssertion(assertionFailure)
+        val result = DataStore.getResult(TEST_SCENARIO)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+
+        assertExceptionMessage(firstAssertionResult, Consts.FAILURE)
+        assertExceptionMessage(lastAssertionResult, Consts.FAILURE)
+        Truth.assertWithMessage("Same exception")
+            .that(firstAssertionResult)
+            .hasMessageThat()
+            .isEqualTo(lastAssertionResult?.message)
+    }
+
+    @Test
+    fun updatesRunStatusFailureFirst() {
+        val runner = CachedAssertionRunner(TEST_SCENARIO)
+        val firstAssertionResult = runner.runAssertion(assertionFailure)
+        val lastAssertionResult = runner.runAssertion(assertionSuccess)
+        val result = DataStore.getResult(TEST_SCENARIO)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        assertExceptionMessage(firstAssertionResult, Consts.FAILURE)
+        Truth.assertWithMessage("Expected exception").that(lastAssertionResult).isNull()
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+    }
+
+    @Test
+    fun updatesRunStatusFailureLast() {
+        val runner = CachedAssertionRunner(TEST_SCENARIO)
+        val firstAssertionResult = runner.runAssertion(assertionSuccess)
+        val lastAssertionResult = runner.runAssertion(assertionFailure)
+        val result = DataStore.getResult(TEST_SCENARIO)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
+        Truth.assertWithMessage("Expected exception").that(firstAssertionResult).isNull()
+        assertExceptionMessage(lastAssertionResult, Consts.FAILURE)
+        Truth.assertWithMessage("Run status")
+            .that(result.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+    }
+
+    companion object {
+        private fun newAssertionData(assertion: (FlickerSubject) -> Unit) =
+            AssertionData(Tag.ALL, EventLogSubject::class, assertion)
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultReaderTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultReaderTest.kt
new file mode 100644
index 0000000..ee3f980
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultReaderTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.TestTraces
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.newTestResultWriter
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [CachedResultReaderTest] */
+@SuppressLint("VisibleForTests")
+class CachedResultReaderTest {
+    @Before
+    fun setup() {
+        DataStore.clear()
+    }
+
+    @Test
+    fun readFromStore() {
+        val writer = newTestResultWriter()
+        writer.addTraceResult(TraceType.EVENT_LOG, TestTraces.EventLog.FILE)
+        val result = writer.write()
+        DataStore.addResult(TEST_SCENARIO, result)
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        val actual = reader.readEventLogTrace()
+        Truth.assertWithMessage("Event log size").that(actual).isNotNull()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultWriterTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultWriterTest.kt
new file mode 100644
index 0000000..7f65131
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/datastore/CachedResultWriterTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertExceptionMessage
+import android.tools.assertThrows
+import android.tools.newTestCachedResultWriter
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [CachedResultWriterTest] */
+@SuppressLint("VisibleForTests")
+class CachedResultWriterTest {
+    @Before
+    fun setup() {
+        DataStore.clear()
+    }
+
+    @Test
+    fun writeToStore() {
+        val writer = newTestCachedResultWriter()
+        val expected = writer.write()
+        Truth.assertWithMessage("Has key in store")
+            .that(DataStore.containsResult(TEST_SCENARIO))
+            .isTrue()
+        val actual = DataStore.getResult(TEST_SCENARIO)
+        Truth.assertWithMessage("Has key in store").that(expected).isEqualTo(actual)
+    }
+
+    @Test
+    fun writeToStoreFailsWhenWriteTwice() {
+        val writer = newTestCachedResultWriter()
+        val failure =
+            assertThrows<IllegalStateException> {
+                writer.write()
+                writer.write()
+            }
+        Truth.assertWithMessage("Has key in store")
+            .that(DataStore.containsResult(TEST_SCENARIO))
+            .isTrue()
+        assertExceptionMessage(failure, TEST_SCENARIO.toString())
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/datastore/Consts.kt b/libraries/flicker/test/src/android/tools/device/flicker/datastore/Consts.kt
new file mode 100644
index 0000000..f7f745d
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/datastore/Consts.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.tools.common.CrossPlatform
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TransitionTimeRange
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.io.ResultData
+
+object Consts {
+    internal const val FAILURE = "Expected failure"
+
+    internal val TEST_RESULT =
+        ResultData(
+            _artifact = getDefaultFlickerOutputDir(),
+            _transitionTimeRange =
+                TransitionTimeRange(
+                    CrossPlatform.timestamp.empty(),
+                    CrossPlatform.timestamp.empty()
+                ),
+            _executionError = null,
+            _runStatus = RunStatus.RUN_EXECUTED
+        )
+
+    internal val RESULT_FAILURE =
+        ResultData(
+            _artifact = getDefaultFlickerOutputDir(),
+            _transitionTimeRange =
+                TransitionTimeRange(
+                    CrossPlatform.timestamp.empty(),
+                    CrossPlatform.timestamp.empty()
+                ),
+            _executionError = null,
+            _runStatus = RunStatus.RUN_EXECUTED
+        )
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/datastore/DataStoreTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/datastore/DataStoreTest.kt
new file mode 100644
index 0000000..e345f78
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/datastore/DataStoreTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.device.flicker.datastore
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertExceptionMessage
+import android.tools.assertThrows
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+@SuppressLint("VisibleForTests")
+class DataStoreTest {
+    @Before
+    fun setup() {
+        DataStore.clear()
+    }
+
+    @Test
+    fun addsElement() {
+        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
+        Truth.assertWithMessage("Contains result")
+            .that(DataStore.containsResult(TEST_SCENARIO))
+            .isTrue()
+    }
+
+    @Test
+    fun throwsErrorAddElementTwice() {
+        val failure =
+            assertThrows<IllegalStateException> {
+                DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
+                DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
+            }
+        Truth.assertWithMessage("Contains result")
+            .that(DataStore.containsResult(TEST_SCENARIO))
+            .isTrue()
+        assertExceptionMessage(failure, TEST_SCENARIO.toString())
+    }
+
+    @Test
+    fun getsElement() {
+        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
+        val actual = DataStore.getResult(TEST_SCENARIO)
+        Truth.assertWithMessage("Expected result").that(actual).isEqualTo(Consts.TEST_RESULT)
+    }
+
+    @Test
+    fun getsElementThrowErrorDoesNotExist() {
+        val failure = assertThrows<IllegalStateException> { DataStore.getResult(TEST_SCENARIO) }
+        assertExceptionMessage(failure, TEST_SCENARIO.toString())
+    }
+
+    @Test
+    fun replacesElement() {
+        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
+        DataStore.replaceResult(TEST_SCENARIO, Consts.RESULT_FAILURE)
+        val actual = DataStore.getResult(TEST_SCENARIO)
+        Truth.assertWithMessage("Expected value").that(actual).isEqualTo(Consts.RESULT_FAILURE)
+    }
+
+    @Test
+    fun replacesElementThrowErrorDoesNotExist() {
+        val failure =
+            assertThrows<IllegalStateException> {
+                DataStore.replaceResult(TEST_SCENARIO, Consts.RESULT_FAILURE)
+            }
+        assertExceptionMessage(failure, TEST_SCENARIO.toString())
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/integration/AssertionErrorTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/integration/AssertionErrorTest.kt
new file mode 100644
index 0000000..f27325e
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/integration/AssertionErrorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.device.flicker.integration
+
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.common.io.RunStatus
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Integration tests to ensure assertions fail correctly
+ *
+ * To run this test: `atest FlickerLibTest:AssertionErrorTest`
+ */
+class AssertionErrorTest {
+    private var assertionExecuted = false
+    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
+
+    @Before
+    fun setup() {
+        assertionExecuted = false
+    }
+
+    @Test
+    fun executesTransition() {
+        Truth.assertWithMessage("Transition executed")
+            .that(AssertionErrorTest.Companion.transitionExecuted)
+            .isTrue()
+        assertArtifactExists()
+    }
+
+    @Test
+    fun assertThrowsAssertionError() {
+        val result = runCatching {
+            testParam.assertLayers {
+                assertionExecuted = true
+                error(Utils.FAILURE)
+            }
+        }
+
+        Truth.assertWithMessage("Executed").that(assertionExecuted).isTrue()
+        Truth.assertWithMessage("Expected exception").that(result.isSuccess).isFalse()
+        Truth.assertWithMessage("Expected exception")
+            .that(result.exceptionOrNull())
+            .hasMessageThat()
+            .contains(Utils.FAILURE)
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Run status")
+            .that(reader.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_FAILED)
+        assertArtifactExists()
+    }
+
+    private fun assertArtifactExists() {
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        val file = File(reader.artifactPath)
+        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
+    }
+
+    companion object {
+        private var transitionExecuted = false
+        @BeforeClass
+        @JvmStatic
+        fun runTransition() =
+            Utils.runTransition { AssertionErrorTest.Companion.transitionExecuted = true }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/integration/FullTestRun.kt b/libraries/flicker/test/src/android/tools/device/flicker/integration/FullTestRun.kt
new file mode 100644
index 0000000..93547e5
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/integration/FullTestRun.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.device.flicker.integration
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Scenario
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.FlickerSubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.flicker.subject.wm.WindowManagerTraceSubject
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class FullTestRun(private val flicker: FlickerTest) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp: CalculatorAppHelper = CalculatorAppHelper(instrumentation)
+    private val tapl: LauncherInstrumentation = LauncherInstrumentation()
+
+    init {
+        flicker.scenario.setIsTablet(
+            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
+                .currentState
+                .wmState
+                .isTablet
+        )
+        tapl.setExpectedRotationCheckEnabled(true)
+    }
+
+    /**
+     * Entry point for the test runner. It will use this method to initialize and cache flicker
+     * executions
+     */
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+    }
+
+    /**
+     * This is a shel test from the flicker infra to ensure the WM tracing pipeline executed
+     * entirely executed correctly
+     */
+    @Presubmit
+    @Test
+    fun internalWmCheck() {
+        var trace: WindowManagerTraceSubject? = null
+        var executionCount = 0
+        flicker.assertWm {
+            executionCount++
+            trace = this
+            this.isNotEmpty()
+        }
+        flicker.assertWm {
+            executionCount++
+            val failure: Result<Any> = runCatching { this.isEmpty() }
+            if (failure.isSuccess) {
+                error("Should have thrown failure")
+            }
+        }
+        flicker.assertWmStart {
+            executionCount++
+            validateState(this, trace?.first())
+            validateVisibleRegion(this.visibleRegion(), trace?.first()?.visibleRegion())
+        }
+        flicker.assertWmEnd {
+            executionCount++
+            validateState(this, trace?.last())
+            validateVisibleRegion(this.visibleRegion(), trace?.last()?.visibleRegion())
+        }
+        Truth.assertWithMessage("Execution count").that(executionCount).isEqualTo(4)
+    }
+
+    /**
+     * This is a shel test from the flicker infra to ensure the Layers tracing pipeline executed
+     * entirely executed correctly
+     */
+    @Presubmit
+    @Test
+    fun internalLayersCheck() {
+        var trace: LayersTraceSubject? = null
+        var executionCount = 0
+        flicker.assertLayers {
+            executionCount++
+            trace = this
+            this.isNotEmpty()
+        }
+        flicker.assertLayers {
+            executionCount++
+            val failure: Result<Any> = runCatching { this.isEmpty() }
+            if (failure.isSuccess) {
+                error("Should have thrown failure")
+            }
+        }
+        flicker.assertLayersStart {
+            executionCount++
+            validateState(this, trace?.first())
+            validateVisibleRegion(this.visibleRegion(), trace?.first()?.visibleRegion())
+        }
+        flicker.assertLayersEnd {
+            executionCount++
+            validateState(this, trace?.last())
+            validateVisibleRegion(this.visibleRegion(), trace?.last()?.visibleRegion())
+        }
+        Truth.assertWithMessage("Execution count").that(executionCount).isEqualTo(4)
+    }
+
+    private fun validateState(actual: FlickerSubject?, expected: FlickerSubject?) {
+        Truth.assertWithMessage("Actual state").that(actual).isNotNull()
+        Truth.assertWithMessage("Expected state").that(expected).isNotNull()
+        Truth.assertWithMessage("Incorrect state")
+            .that(actual?.completeFacts?.joinToString { it.toString() })
+            .isEqualTo(expected?.completeFacts?.joinToString { it.toString() })
+    }
+
+    private fun validateVisibleRegion(
+        actual: RegionSubject?,
+        expected: RegionSubject?,
+    ) {
+        Truth.assertWithMessage("Actual visible region").that(actual).isNotNull()
+        Truth.assertWithMessage("Expected visible region").that(expected).isNotNull()
+        actual?.coversExactly(expected?.region ?: Region.EMPTY)
+
+        val failure: Result<Any?> = runCatching {
+            actual?.isHigher(expected?.region ?: Region.EMPTY)
+        }
+        if (failure.isSuccess) {
+            error("Should have thrown failure")
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                extraArgs = mapOf(Scenario.FAAS_BLOCKING to true)
+            )
+        }
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/integration/NoErrorTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/integration/NoErrorTest.kt
new file mode 100644
index 0000000..795ecd1
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/integration/NoErrorTest.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.device.flicker.integration
+
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.common.io.RunStatus
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Contains an integration test
+ *
+ * To run this test: `atest FlickerLibTest:IntegrationTests`
+ */
+@RunWith(MockitoJUnitRunner::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NoErrorTest {
+    private var assertionExecuted = false
+    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
+
+    @Before
+    fun setup() {
+        assertionExecuted = false
+    }
+
+    @Test
+    fun executesTransition() {
+        Truth.assertWithMessage("Transition executed").that(transitionExecuted).isTrue()
+    }
+
+    @Test
+    fun assertSetupAndTearDownTestAppNeverExists() {
+        assertPredicatePasses {
+            testParam.assertWm {
+                assertionExecuted = true
+                require(
+                    this.subjects.none {
+                        it.wmState
+                            .getActivitiesForWindow(Utils.setupAndTearDownTestApp.componentMatcher)
+                            .isNotEmpty()
+                    }
+                ) {
+                    "${Utils.setupAndTearDownTestApp.appName} window existed at some point " +
+                        "but shouldn't have."
+                }
+            }
+        }
+    }
+
+    @Test
+    fun assertTransitionTestAppExistsAtSomePoint() {
+        assertPredicatePasses {
+            testParam.assertWm {
+                assertionExecuted = true
+                require(
+                    this.subjects.any {
+                        it.wmState
+                            .getActivitiesForWindow(Utils.transitionTestApp.componentMatcher)
+                            .isNotEmpty()
+                    }
+                ) {
+                    "${Utils.transitionTestApp.appName} window didn't exist at any point " +
+                        "but should have."
+                }
+            }
+        }
+    }
+
+    @Test
+    fun assertSetupAndTearDownTestLayerNeverExists() {
+        assertPredicatePasses {
+            testParam.assertLayers {
+                assertionExecuted = true
+                require(
+                    this.subjects.none {
+                        Utils.setupAndTearDownTestApp.componentMatcher.layerMatchesAnyOf(
+                            it.entry.flattenedLayers.filter { layer -> layer.isVisible }
+                        )
+                    }
+                ) {
+                    "${Utils.setupAndTearDownTestApp.appName} layer was visible at some point " +
+                        "but shouldn't have."
+                }
+
+                require(
+                    this.subjects.none {
+                        Utils.setupAndTearDownTestApp.componentMatcher.layerMatchesAnyOf(
+                            it.entry.flattenedLayers
+                        )
+                    }
+                ) {
+                    "${Utils.setupAndTearDownTestApp.appName} layer existed at some point " +
+                        "but shouldn't have."
+                }
+            }
+        }
+    }
+
+    @Test
+    fun assertTransitionTestLayerExistsAtSomePoint() {
+        assertPredicatePasses {
+            testParam.assertLayers {
+                assertionExecuted = true
+                require(
+                    this.subjects.any {
+                        Utils.transitionTestApp.componentMatcher.layerMatchesAnyOf(
+                            it.entry.flattenedLayers
+                        )
+                    }
+                ) {
+                    "${Utils.transitionTestApp.appName} layer didn't exist at any point " +
+                        "but should have."
+                }
+            }
+        }
+    }
+
+    @Test
+    fun assertStartStateLayersIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertLayersStart {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertEndStateLayersIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertLayersEnd {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertStartStateWmIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertWmStart {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertEndStateWmIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertWmEnd {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertTagStateLayersIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertLayersTag(Utils.TAG) {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertTagStateWmIsNotEmpty() {
+        assertPredicatePasses {
+            testParam.assertWmTag(Utils.TAG) {
+                assertionExecuted = true
+                isNotEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun assertEventLog() {
+        assertPredicatePasses { testParam.assertEventLog { assertionExecuted = true } }
+    }
+
+    private fun assertPredicatePasses(predicate: () -> Unit) {
+        predicate.invoke()
+        Truth.assertWithMessage("Executed").that(assertionExecuted).isTrue()
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Run status")
+            .that(reader.runStatus)
+            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
+        assertArtifactExists()
+    }
+
+    private fun assertArtifactExists() {
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        val file = File(reader.artifactPath)
+        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
+    }
+
+    companion object {
+        private var transitionExecuted = false
+        @BeforeClass
+        @JvmStatic
+        fun runTransition() = Utils.runTransition { transitionExecuted = true }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/integration/TransitionErrorTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/integration/TransitionErrorTest.kt
new file mode 100644
index 0000000..a1334a6
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/integration/TransitionErrorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.device.flicker.integration
+
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.common.io.RunStatus
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+class TransitionErrorTest {
+    private var assertionExecuted = false
+    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
+
+    @Before
+    fun setup() {
+        assertionExecuted = false
+    }
+
+    @Test
+    fun failsToExecuteTransition() {
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertArtifactExists()
+    }
+
+    @Test
+    fun assertThrowsTransitionError() {
+        val results =
+            (0..10).map {
+                runCatching {
+                    testParam.assertLayers {
+                        assertionExecuted = true
+                        error(WRONG_EXCEPTION)
+                    }
+                }
+            }
+
+        Truth.assertWithMessage("Executed").that(assertionExecuted).isFalse()
+        results.forEach { result ->
+            Truth.assertWithMessage("Expected exception").that(result.isFailure).isTrue()
+            Truth.assertWithMessage("Expected exception")
+                .that(result.exceptionOrNull())
+                .hasMessageThat()
+                .contains(Utils.FAILURE)
+        }
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertArtifactExists()
+    }
+
+    private fun assertArtifactExists() {
+        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
+        val file = File(reader.artifactPath)
+        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
+    }
+
+    companion object {
+        private const val WRONG_EXCEPTION = "Wrong exception"
+
+        @BeforeClass @JvmStatic fun runTransition() = Utils.runTransition { error(Utils.FAILURE) }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/integration/Utils.kt b/libraries/flicker/test/src/android/tools/device/flicker/integration/Utils.kt
new file mode 100644
index 0000000..e1a6bd9
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/integration/Utils.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.device.flicker.integration
+
+import android.annotation.SuppressLint
+import android.app.Instrumentation
+import android.tools.TEST_SCENARIO
+import android.tools.device.apphelpers.BrowserAppHelper
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.runner.TransitionRunner
+import android.tools.device.traces.executeShellCommand
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.runner.Description
+
+@SuppressLint("VisibleForTests")
+object Utils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    internal const val TAG = "tag"
+    internal const val FAILURE = "Expected failure"
+    internal val setupAndTearDownTestApp = BrowserAppHelper(instrumentation)
+    internal val transitionTestApp = MessagingAppHelper(instrumentation)
+
+    private fun createFlicker(onExecuted: () -> Unit) =
+        FlickerBuilder(instrumentation)
+            .apply {
+                setup {
+                    // Shouldn't be in the trace we run assertions on
+                    setupAndTearDownTestApp.launchViaIntent(wmHelper)
+                    setupAndTearDownTestApp.exit(wmHelper)
+                }
+                transitions {
+                    // Should be in the trace we run assertions on
+                    transitionTestApp.launchViaIntent(wmHelper)
+                    createTag(TAG)
+                    onExecuted()
+                }
+                teardown {
+                    // Shouldn't be in the trace we run assertions on
+                    setupAndTearDownTestApp.launchViaIntent(wmHelper)
+                    setupAndTearDownTestApp.exit(wmHelper)
+                }
+            }
+            .build()
+
+    fun runTransition(onExecuted: () -> Unit) {
+        DataStore.clear()
+        val flicker = createFlicker(onExecuted)
+
+        // Clear the trace output directory
+        executeShellCommand("rm -rf ${flicker.outputDir}")
+
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation)
+        runner.execute(flicker, Description.createTestDescription(this::class.java, "test"))
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt
new file mode 100644
index 0000000..e26fbc4
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt
@@ -0,0 +1,334 @@
+/*
+ * 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.device.flicker.junit
+
+import android.annotation.SuppressLint
+import android.app.Instrumentation
+import android.tools.InitRule
+import android.tools.device.apphelpers.BrowserAppHelper
+import android.tools.device.flicker.IS_FAAS_ENABLED
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.getScenarioTraces
+import androidx.test.filters.FlakyTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.Assume
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runner.manipulation.Filter
+import org.junit.runner.notification.RunNotifier
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import org.junit.runners.model.TestClass
+import org.junit.runners.parameterized.TestWithParameters
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Contains [FlickerBlockJUnit4ClassRunner] tests.
+ *
+ * To run this test: `atest FlickerLibTest:FlickerBlockJUnit4ClassRunnerTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressLint("VisibleForTests")
+class FlickerBlockJUnit4ClassRunnerTest {
+    @Test
+    fun doesNotRunWithEmptyTestParameter() {
+        val testClass = TestClass(SimpleFaasTest::class.java)
+        val test = TestWithParameters("[PARAMS]", testClass, listOf())
+        try {
+            val runner = createRunner(test)
+            runner.run(RunNotifier())
+            error("Expected runner to fail but did not")
+        } catch (e: Throwable) {
+            Truth.assertWithMessage("Expected failure")
+                .that(e)
+                .hasMessageThat()
+                .contains(NO_SCENARIO_MESSAGE)
+        }
+    }
+
+    @Test
+    fun doesNotRunWithoutValidFlickerTest() {
+        val testClass = TestClass(SimpleFaasTest::class.java)
+        val test = TestWithParameters("[PARAMS]", testClass, listOf("invalid param"))
+        try {
+            val runner = createRunner(test)
+            runner.run(RunNotifier())
+            error("Expected runner to fail but did not")
+        } catch (e: Throwable) {
+            Truth.assertWithMessage("Expected failure")
+                .that(e)
+                .hasMessageThat()
+                .contains(NO_SCENARIO_MESSAGE)
+        }
+    }
+
+    @Test
+    fun runsWithValidFlickerTest() {
+        val testClass = TestClass(SimpleFaasTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+        val runner = createRunner(test)
+        runner.run(RunNotifier())
+    }
+
+    @Test
+    fun flakyTestsRunWithNoFilter() {
+        val testClass = TestClass(SimpleTestWithFlakyTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+        val runner = createRunner(test)
+        flakyTestRuns = 0
+        runner.run(RunNotifier())
+        Truth.assertThat(runner.testCount()).isEqualTo(2)
+        Truth.assertThat(flakyTestRuns).isEqualTo(1)
+    }
+
+    @Test
+    fun canFilterOutFlakyTests() {
+        val testClass = TestClass(SimpleTestWithFlakyTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+        val runner = createRunner(test)
+        runner.filter(FLAKY_TEST_FILTER)
+        flakyTestRuns = 0
+        val notifier = mock(RunNotifier::class.java)
+        runner.run(notifier)
+        Truth.assertThat(runner.testCount()).isEqualTo(1)
+        Truth.assertThat(flakyTestRuns).isEqualTo(0)
+        verify(notifier, never())
+            .fireTestStarted(
+                argThat { description -> description.methodName.contains("flakyTest") }
+            )
+    }
+
+    @FlakyTest
+    @Test
+    fun injectsFlickerServiceTests() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        Assume.assumeTrue(IS_FAAS_ENABLED)
+
+        val testClass = TestClass(SimpleFaasTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+        val runner = createRunner(test)
+        val notifier = mock(RunNotifier::class.java)
+        runner.run(notifier)
+        Truth.assertThat(runner.testCount()).isAtLeast(2)
+        verify(notifier).fireTestStarted(argThat { it.methodName.contains("test") })
+        verify(notifier).fireTestFinished(argThat { it.methodName.contains("test") })
+        verify(notifier, atLeast(1)).fireTestStarted(argThat { it.methodName.contains("FaaS") })
+        verify(notifier, atLeast(1)).fireTestFinished(argThat { it.methodName.contains("FaaS") })
+    }
+
+    /*@Test
+    fun injectedFlickerTestsAreNotExcludedByFilter() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        Assume.assumeTrue(IS_FAAS_ENABLED)
+
+        val testClass = TestClass(SimpleFaasTestWithFlakyTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+        val runner = FlickerBlockJUnit4ClassRunner(test)
+        runner.filter(FLAKY_TEST_FILTER)
+        val notifier = mock(RunNotifier::class.java)
+        runner.run(notifier)
+        val executionErrors = runner.executionErrors()
+        if (executionErrors.isNotEmpty()) {
+            throw executionErrors.first()
+        }
+        Truth.assertThat(runner.testCount()).isAtLeast(2)
+        verify(notifier).fireTestStarted(argThat { it.methodName.contains("test") })
+        verify(notifier).fireTestFinished(argThat { it.methodName.contains("test") })
+        verify(notifier, atLeast(1)).fireTestStarted(argThat { it.methodName.contains("FaaS") })
+        verify(notifier, atLeast(1)).fireTestFinished(argThat { it.methodName.contains("FaaS") })
+        verify(notifier, never())
+            .fireTestStarted(
+                argThat { description -> description.methodName.contains("flakyTest") }
+            )
+    }
+
+    @Test
+    fun transitionNotRerunWithFaasEnabled() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        Assume.assumeTrue(IS_FAAS_ENABLED)
+
+        transitionRunCount = 0
+        val testClass = TestClass(TransitionRunCounterWithFaasTest::class.java)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
+
+        val runner = FlickerBlockJUnit4ClassRunner(test)
+        runner.run(RunNotifier())
+        Truth.assertThat(parameters[0].flicker.faasEnabled).isTrue()
+        val executionError = parameters[0].flicker.result!!.transitionExecutionError
+        Truth.assertWithMessage(
+                "No flicker execution errors were expected but got some ::$executionError"
+            )
+            .that(executionError)
+            .isNull()
+
+        Assert.assertEquals(1, transitionRunCount)
+        transitionRunCount = 0
+    }*/
+
+    @Test
+    fun reportsExecutionErrors() {
+        checkTestRunReportsExecutionErrors(AlwaysFailExecutionTestClass::class.java)
+    }
+
+    private fun checkTestRunReportsExecutionErrors(klass: Class<*>) {
+        val testClass = TestClass(klass)
+        val parameters = FlickerTestFactory.nonRotationTests()
+        val flickerTest = parameters.first()
+        val test = TestWithParameters("[PARAMS]", testClass, listOf(flickerTest))
+
+        val runner = createRunner(test)
+        val notifier = mock(RunNotifier::class.java)
+
+        runner.run(notifier)
+        verify(notifier)
+            .fireTestFailure(
+                argThat { failure ->
+                    failure.message.contains(TRANSITION_FAILURE_MESSAGE) &&
+                        failure.description.isTest &&
+                        failure.description.displayName ==
+                            "test[${flickerTest.scenario.description}](${klass.name})"
+                }
+            )
+    }
+
+    /** Below are all the mock test classes uses for testing purposes */
+    @RunWith(Parameterized::class)
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    open class SimpleTest(protected val flicker: FlickerTest) {
+        val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+        private val testApp: BrowserAppHelper = BrowserAppHelper(instrumentation)
+
+        @FlickerBuilderProvider
+        open fun buildFlicker(): FlickerBuilder {
+            return FlickerBuilder(instrumentation).usingExistingTraces {
+                getScenarioTraces("AppLaunch")
+            }
+        }
+
+        @Test
+        fun test() {
+            flicker.assertWm {
+                // Random test to make sure flicker transition is executed
+                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+            }
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    @FlickerServiceCompatible
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    open class SimpleFaasTest(flicker: FlickerTest) : SimpleTest(flicker)
+
+    @RunWith(Parameterized::class)
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    class AlwaysFailExecutionTestClass(private val flicker: FlickerTest) {
+        val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+        @FlickerBuilderProvider
+        fun buildFlicker(): FlickerBuilder {
+            return FlickerBuilder(instrumentation).apply {
+                withoutScreenRecorder()
+                transitions { error(TRANSITION_FAILURE_MESSAGE) }
+            }
+        }
+
+        @Test
+        fun test() {
+            flicker.assertWm {
+                // Random test to make sure flicker transition is executed
+                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+            }
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    open class SimpleTestWithFlakyTest(flicker: FlickerTest) : SimpleTest(flicker) {
+        @FlakyTest
+        @Test
+        fun flakyTest() {
+            flakyTestRuns++
+            flicker.assertWm {
+                // Random test to make sure flicker transition is executed
+                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+            }
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    @FlickerServiceCompatible
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    class SimpleFaasTestWithFlakyTest(flicker: FlickerTest) : SimpleTestWithFlakyTest(flicker)
+
+    @RunWith(Parameterized::class)
+    @FlickerServiceCompatible
+    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+    class TransitionRunCounterWithFaasTest(flicker: FlickerTest) : SimpleFaasTest(flicker) {
+        @FlickerBuilderProvider
+        override fun buildFlicker(): FlickerBuilder {
+            return FlickerBuilder(instrumentation).apply { transitions { transitionRunCount++ } }
+        }
+    }
+
+    companion object {
+        const val TRANSITION_FAILURE_MESSAGE = "Transition execution failed"
+        private val NO_SCENARIO_MESSAGE = "Unable to extract ${FlickerTest::class.simpleName}"
+
+        val FLAKY_TEST_FILTER =
+            object : Filter() {
+                override fun shouldRun(description: Description): Boolean {
+                    val hasFlakyAnnotation =
+                        description.annotations.filterIsInstance<FlakyTest>().isNotEmpty()
+                    if (hasFlakyAnnotation && description.isTest) {
+                        return false // filter out
+                    }
+                    return true
+                }
+
+                override fun describe(): String {
+                    return "no flaky tests"
+                }
+            }
+
+        var transitionRunCount = 0
+        var flakyTestRuns = 0
+
+        private fun createRunner(baseTest: TestWithParameters) =
+            FlickerParametersRunnerFactory().createRunnerForTestWithParameters(baseTest)
+                as FlickerBlockJUnit4ClassRunner
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerServiceDecoratorTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerServiceDecoratorTest.kt
new file mode 100644
index 0000000..2707948
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/junit/FlickerServiceDecoratorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.device.flicker.junit
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import com.google.common.truth.Truth
+import kotlin.reflect.KClass
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.runners.model.TestClass
+import org.junit.runners.parameterized.TestWithParameters
+
+/** Tests for [FlickerServiceDecorator] */
+@SuppressLint("VisibleForTests")
+class FlickerServiceDecoratorTest {
+    @Before
+    fun setup() {
+        DataStore.clear()
+    }
+
+    @Test
+    fun passValidClass() {
+        val test =
+            TestWithParameters(
+                "test",
+                TestClass(TestUtils.DummyTestClassValid::class.java),
+                listOf(TestUtils.VALID_ARGS_EMPTY)
+            )
+        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
+        var failures = decorator.doValidateConstructor()
+        Truth.assertWithMessage("Failure count").that(failures).isEmpty()
+
+        failures = decorator.doValidateInstanceMethods()
+        Truth.assertWithMessage("Failure count").that(failures).isEmpty()
+    }
+
+    @Test
+    fun hasUniqueMethodNames() {
+        val test =
+            TestWithParameters(
+                "test",
+                TestClass(FlickerBlockJUnit4ClassRunnerTest.SimpleFaasTest::class.java),
+                listOf(TestUtils.VALID_ARGS_EMPTY)
+            )
+        val decorator = FlickerServiceDecorator(test.testClass, TEST_SCENARIO, inner = null)
+        val methods =
+            decorator.getTestMethods(
+                FlickerBlockJUnit4ClassRunnerTest.SimpleFaasTest(FlickerTest())
+            )
+        val duplicatedMethods = methods.groupBy { it.name }.filter { it.value.size > 1 }
+
+        if (isShellTransitionsEnabled) {
+            Truth.assertWithMessage("Methods").that(methods).isNotEmpty()
+        }
+        Truth.assertWithMessage("Unique methods").that(duplicatedMethods).isEmpty()
+    }
+
+    @Test
+    fun failNoProviderMethods() {
+        assertFailProviderMethod(
+            TestUtils.DummyTestClassEmpty::class,
+            expectedExceptions =
+                listOf("One object should be annotated with @FlickerBuilderProvider")
+        )
+    }
+
+    @Test
+    fun failMultipleProviderMethods() {
+        assertFailProviderMethod(
+            TestUtils.DummyTestClassMultipleProvider::class,
+            expectedExceptions =
+                listOf("Only one object should be annotated with @FlickerBuilderProvider")
+        )
+    }
+
+    @Test
+    fun failStaticProviderMethod() {
+        assertFailProviderMethod(
+            TestUtils.DummyTestClassProviderStatic::class,
+            expectedExceptions = listOf("Method myMethod() should not be static")
+        )
+    }
+
+    @Test
+    fun failPrivateProviderMethod() {
+        assertFailProviderMethod(
+            TestUtils.DummyTestClassProviderPrivateVoid::class,
+            expectedExceptions =
+                listOf(
+                    "Method myMethod() should be public",
+                    "Method myMethod() should return a " +
+                        "${FlickerBuilder::class.java.simpleName} object"
+                )
+        )
+    }
+
+    @Test
+    fun failConstructorWithNoArguments() {
+        assertFailConstructor(emptyList())
+    }
+
+    @Test
+    fun failWithInvalidConstructorArgument() {
+        assertFailConstructor(listOf(1, 2, 3))
+    }
+
+    private fun assertFailProviderMethod(cls: KClass<*>, expectedExceptions: List<String>) {
+        val test =
+            TestWithParameters("test", TestClass(cls.java), listOf(TestUtils.VALID_ARGS_EMPTY))
+        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
+        val failures = decorator.doValidateInstanceMethods()
+        Truth.assertWithMessage("Failure count").that(failures).hasSize(expectedExceptions.count())
+        expectedExceptions.forEachIndexed { idx, expectedException ->
+            val failure = failures[idx]
+            Truth.assertWithMessage("Failure")
+                .that(failure)
+                .hasMessageThat()
+                .contains(expectedException)
+        }
+    }
+
+    private fun assertFailConstructor(args: List<Any>) {
+        val test =
+            TestWithParameters("test", TestClass(TestUtils.DummyTestClassEmpty::class.java), args)
+        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
+        val failures = decorator.doValidateConstructor()
+
+        Truth.assertWithMessage("Failure count").that(failures).hasSize(1)
+
+        val failure = failures.first()
+        Truth.assertWithMessage("Expected failure")
+            .that(failure)
+            .hasMessageThat()
+            .contains(
+                "Constructor should have a parameter of type ${FlickerTest::class.simpleName}"
+            )
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/junit/LegacyFlickerDecoratorTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/junit/LegacyFlickerDecoratorTest.kt
new file mode 100644
index 0000000..ac4dba0
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/junit/LegacyFlickerDecoratorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.device.flicker.junit
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.common.ScenarioBuilder
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.flicker.legacy.FlickerTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.TestClass
+import org.junit.runners.parameterized.TestWithParameters
+
+/** Tests for [LegacyFlickerDecorator] */
+@SuppressLint("VisibleForTests")
+class LegacyFlickerDecoratorTest {
+    @Before
+    fun setup() {
+        DataStore.clear()
+    }
+
+    @Test
+    fun hasNoTestMethods() {
+        val scenario =
+            ScenarioBuilder().forClass(TestUtils.DummyTestClassValid::class.java.name).build()
+        val test =
+            TestWithParameters(
+                "test",
+                TestClass(TestUtils.DummyTestClassValid::class.java),
+                listOf(TestUtils.VALID_ARGS_EMPTY)
+            )
+        val helper = LegacyFlickerDecorator(test.testClass, scenario, inner = null)
+        Truth.assertWithMessage("Test method count").that(helper.getTestMethods(Any())).isEmpty()
+    }
+
+    @Test
+    fun runTransitionAndAddToDatastore() {
+        val scenario =
+            ScenarioBuilder().forClass(TestUtils.DummyTestClassValid::class.java.name).build()
+        val test =
+            TestWithParameters(
+                "test",
+                TestClass(TestUtils.DummyTestClassValid::class.java),
+                listOf(TestUtils.VALID_ARGS_EMPTY)
+            )
+        val helper = LegacyFlickerDecorator(test.testClass, scenario, inner = null)
+        TestUtils.executionCount = 0
+        val method =
+            FrameworkMethod(TestUtils.DummyTestClassValid::class.java.getMethod("dummyExecute"))
+        repeat(3) {
+            helper
+                .getMethodInvoker(
+                    method,
+                    test = TestUtils.DummyTestClassValid(FlickerTest()),
+                )
+                .evaluate()
+        }
+
+        Truth.assertWithMessage("Executed").that(TestUtils.executionCount).isEqualTo(1)
+        Truth.assertWithMessage("In Datastore")
+            .that(DataStore.containsResult(TestUtils.DummyTestClassValid.SCENARIO))
+            .isTrue()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/junit/TestUtils.kt b/libraries/flicker/test/src/android/tools/device/flicker/junit/TestUtils.kt
new file mode 100644
index 0000000..97305d6
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/junit/TestUtils.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.device.flicker.junit
+
+import android.app.Instrumentation
+import android.tools.common.ScenarioBuilder
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+object TestUtils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    val VALID_ARGS_EMPTY = FlickerTest()
+
+    var executionCount = 0
+
+    class DummyTestClassValid(test: FlickerTest) {
+        @FlickerBuilderProvider
+        fun myMethod(): FlickerBuilder =
+            FlickerBuilder(instrumentation).apply { transitions { executionCount++ } }
+
+        fun dummyExecute() {}
+
+        companion object {
+            val SCENARIO = ScenarioBuilder().forClass(DummyTestClassValid::class.java.name).build()
+        }
+    }
+
+    class DummyTestClassEmpty
+
+    class DummyTestClassMultipleProvider {
+        @FlickerBuilderProvider fun myMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
+
+        @FlickerBuilderProvider
+        fun mySecondMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
+    }
+
+    class DummyTestClassProviderPrivateVoid {
+        @FlickerBuilderProvider private fun myMethod() {}
+    }
+
+    class DummyTestClassProviderStatic {
+        companion object {
+            @FlickerBuilderProvider
+            @JvmStatic
+            fun myMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
+        }
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestFactoryTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestFactoryTest.kt
new file mode 100644
index 0000000..9654ea9
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestFactoryTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.tools.InitRule
+import android.tools.common.Rotation
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [FlickerTestFactory] tests.
+ *
+ * To run this test: `atest FlickerLibTest:FlickerTestFactoryRunnerTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FlickerTestFactoryTest {
+    @Test
+    fun checkBuildTest() {
+        val actual = FlickerTestFactory.nonRotationTests()
+        Truth.assertWithMessage("Flicker should create tests for 0 and 90 degrees")
+            .that(actual)
+            .hasSize(4)
+    }
+
+    @Test
+    fun checkBuildRotationTest() {
+        val actual = FlickerTestFactory.rotationTests()
+        Truth.assertWithMessage("Flicker should create tests for 0 and 90 degrees")
+            .that(actual)
+            .hasSize(4)
+    }
+
+    @Test
+    fun checkBuildCustomRotationsTest() {
+        val rotations =
+            listOf(
+                Rotation.ROTATION_0,
+                Rotation.ROTATION_90,
+                Rotation.ROTATION_180,
+                Rotation.ROTATION_270
+            )
+        val actual = FlickerTestFactory.rotationTests(supportedRotations = rotations)
+        // Should have config for each rotation pair
+        Truth.assertWithMessage("Flicker should create tests for 0/90/180/270 degrees")
+            .that(actual)
+            .hasSize(24)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestTest.kt
new file mode 100644
index 0000000..4262e7c
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/FlickerTestTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.device.flicker.legacy
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.TestTraces
+import android.tools.assertExceptionMessage
+import android.tools.assertThrows
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.device.flicker.datastore.CachedResultReader
+import android.tools.device.flicker.datastore.DataStore
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.ResultReader
+import android.tools.newTestCachedResultWriter
+import android.tools.outputFileName
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [FlickerTest] */
+@SuppressLint("VisibleForTests")
+class FlickerTestTest {
+    private var executionCount = 0
+
+    @Before
+    fun setup() {
+        executionCount = 0
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        DataStore.clear()
+    }
+
+    @Test
+    fun failsWithoutScenario() {
+        val actual = FlickerTest()
+        val failure =
+            assertThrows<IllegalArgumentException> { actual.assertLayers { executionCount++ } }
+        assertExceptionMessage(failure, "Scenario shouldn't be empty")
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(0)
+    }
+
+    @Test
+    fun executesLayers() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayers { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.SF,
+            predicate,
+            TestTraces.LayerTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun executesLayerStart() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayersStart { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.SF,
+            predicate,
+            TestTraces.LayerTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun executesLayerEnd() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayersEnd { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.SF,
+            predicate,
+            TestTraces.LayerTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun doesNotExecuteLayersWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayers { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteLayersStartWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayersStart { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteLayersEndWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayersEnd { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteLayerTagWithoutTag() {
+        val predicate: (FlickerTest) -> Unit = { it.assertLayersTag("tag") { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
+    }
+
+    @Test
+    fun executesWm() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWm { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.WM,
+            predicate,
+            TestTraces.WMTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun executesWmStart() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWmStart { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.WM,
+            predicate,
+            TestTraces.WMTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun executesWmEnd() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWmEnd { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.WM,
+            predicate,
+            TestTraces.WMTrace.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun doesNotExecuteWmWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWm { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteWmStartWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWmStart { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteWmEndWithoutTrace() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWmEnd { executionCount++ } }
+        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
+    }
+
+    @Test
+    fun doesNotExecuteWmTagWithoutTag() {
+        val predicate: (FlickerTest) -> Unit = { it.assertWmTag("tag") { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.WM,
+            predicate,
+            TestTraces.WMTrace.FILE,
+            expectedExecutionCount = 0
+        )
+    }
+
+    @Test
+    fun executesEventLog() {
+        val predicate: (FlickerTest) -> Unit = { it.assertEventLog { executionCount++ } }
+        doWriteTraceExecuteAssertionAndVerify(
+            TraceType.EVENT_LOG,
+            predicate,
+            TestTraces.EventLog.FILE,
+            expectedExecutionCount = 2
+        )
+    }
+
+    @Test
+    fun doesNotExecuteEventLogWithoutEventLog() {
+        val predicate: (FlickerTest) -> Unit = { it.assertEventLog { executionCount++ } }
+        newTestCachedResultWriter().write()
+        val flickerWrapper = FlickerTest()
+        flickerWrapper.initialize(TEST_SCENARIO.testClass)
+        // Each assertion is executed independently and not cached, only Flicker as a Service
+        // assertions are cached
+        predicate.invoke(flickerWrapper)
+        predicate.invoke(flickerWrapper)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(0)
+    }
+
+    private fun doExecuteAssertionWithoutTraceAndVerifyNotExecuted(
+        traceType: TraceType,
+        predicate: (FlickerTest) -> Unit
+    ) =
+        doWriteTraceExecuteAssertionAndVerify(
+            traceType,
+            predicate,
+            file = null,
+            expectedExecutionCount = 0
+        )
+
+    private fun doWriteTraceExecuteAssertionAndVerify(
+        traceType: TraceType,
+        predicate: (FlickerTest) -> Unit,
+        file: File?,
+        expectedExecutionCount: Int
+    ) {
+        val writer = newTestCachedResultWriter()
+        if (file != null) {
+            writer.addTraceResult(traceType, file)
+        }
+        writer.write()
+        val flickerWrapper =
+            FlickerTest(
+                resultReaderProvider = {
+                    CachedResultReader(
+                        it,
+                        DEFAULT_TRACE_CONFIG,
+                        reader = ResultReader(DataStore.getResult(it), DEFAULT_TRACE_CONFIG)
+                    )
+                }
+            )
+        flickerWrapper.initialize(TEST_SCENARIO.testClass)
+        // Each assertion is executed independently and not cached, only Flicker as a Service
+        // assertions are cached
+        predicate.invoke(flickerWrapper)
+        predicate.invoke(flickerWrapper)
+
+        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(expectedExecutionCount)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/Consts.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/Consts.kt
new file mode 100644
index 0000000..56ddeff
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/Consts.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import org.junit.runner.Description
+
+object Consts {
+    internal const val FAILURE = "Expected failure"
+    internal const val SETUP = "Setup"
+    internal const val TEARDOWN = "Teardown"
+    internal const val TRANSITION = "Transition"
+
+    internal fun description(obj: Any) = Description.createTestDescription(obj::class.java, "test")
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/SetupTeardownRuleTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/SetupTeardownRuleTest.kt
new file mode 100644
index 0000000..ba960f2
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/SetupTeardownRuleTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertThrows
+import android.tools.device.flicker.legacy.AbstractFlickerTestData
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+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
+import org.mockito.Mockito
+
+/** Tests for [SetupTeardownRule] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SetupTeardownRuleTest {
+    private var setupExecuted = false
+    private var teardownExecuted = false
+
+    private val runSetup: IFlickerTestData.() -> Unit = { setupExecuted = true }
+    private val runTeardown: IFlickerTestData.() -> Unit = { teardownExecuted = true }
+    private val throwError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
+
+    @Before
+    fun setup() {
+        setupExecuted = false
+        teardownExecuted = false
+    }
+
+    @Test
+    fun executesSetupTeardown() {
+        val rule = createRule(listOf(runSetup), listOf(runTeardown))
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        Truth.assertWithMessage("Setup executed").that(setupExecuted).isTrue()
+        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isTrue()
+    }
+
+    @Test
+    fun throwsSetupFailureAndExecutesTeardown() {
+        val failure =
+            assertThrows<TransitionSetupFailure> {
+                val rule = createRule(listOf(throwError, runSetup), listOf(runTeardown))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Setup executed").that(setupExecuted).isFalse()
+        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isTrue()
+    }
+
+    @Test
+    fun throwsTeardownFailure() {
+        val failure =
+            assertThrows<TransitionTeardownFailure> {
+                val rule = createRule(listOf(runSetup), listOf(throwError, runTeardown))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Setup executed").that(setupExecuted).isTrue()
+        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isFalse()
+    }
+
+    companion object {
+        private fun createRule(
+            setupCommands: List<IFlickerTestData.() -> Unit>,
+            teardownCommands: List<IFlickerTestData.() -> Unit>
+        ): SetupTeardownRule {
+            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+            val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
+            return SetupTeardownRule(
+                mockedFlicker,
+                ResultWriter(),
+                TEST_SCENARIO,
+                instrumentation,
+                setupCommands,
+                teardownCommands,
+                WindowManagerStateHelper()
+            )
+        }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TestUtils.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TestUtils.kt
new file mode 100644
index 0000000..2f50f8e
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TestUtils.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.device.traces.io.IResultData
+import com.google.common.truth.Truth
+
+object TestUtils {
+    internal fun validateTransitionTime(result: IResultData) {
+        val startTime = result.transitionTimeRange.start
+        val endTime = result.transitionTimeRange.end
+        validateTimeGreaterThan(startTime, "Start time", CrossPlatform.timestamp.min())
+        validateTimeGreaterThan(endTime, "End time", CrossPlatform.timestamp.min())
+        validateTimeGreaterThan(CrossPlatform.timestamp.max(), "End time", endTime)
+    }
+
+    internal fun validateTransitionTimeIsEmpty(result: IResultData) {
+        val startTime = result.transitionTimeRange.start
+        val endTime = result.transitionTimeRange.end
+        validateEqualTo(startTime, "Start time", CrossPlatform.timestamp.min())
+        validateEqualTo(endTime, "End time", CrossPlatform.timestamp.max())
+    }
+
+    private fun validateEqualTo(time: Timestamp, name: String, expectedValue: Timestamp) {
+        Truth.assertWithMessage("$name - systemUptimeNanos")
+            .that(time.systemUptimeNanos)
+            .isEqualTo(expectedValue.systemUptimeNanos)
+        Truth.assertWithMessage("$name - unixNanos")
+            .that(time.unixNanos)
+            .isEqualTo(expectedValue.unixNanos)
+        Truth.assertWithMessage("$name - elapsedNanos")
+            .that(time.elapsedNanos)
+            .isEqualTo(expectedValue.elapsedNanos)
+    }
+
+    private fun validateTimeGreaterThan(time: Timestamp, name: String, minValue: Timestamp) {
+        Truth.assertWithMessage("$name - systemUptimeNanos")
+            .that(time.systemUptimeNanos)
+            .isGreaterThan(minValue.systemUptimeNanos)
+        Truth.assertWithMessage("$name - unixNanos")
+            .that(time.unixNanos)
+            .isGreaterThan(minValue.unixNanos)
+        Truth.assertWithMessage("$name - elapsedNanos")
+            .that(time.elapsedNanos)
+            .isGreaterThan(minValue.elapsedNanos)
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TraceMonitorRuleTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TraceMonitorRuleTest.kt
new file mode 100644
index 0000000..82ae535
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TraceMonitorRuleTest.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.app.Instrumentation
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertThrows
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+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
+
+/** Tests for [TraceMonitorRule] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TraceMonitorRuleTest {
+    private var startExecutionCount = 0
+    private var setResultExecutionCount = 0
+
+    private val monitorWithExceptionStart =
+        createMonitor({ error(Consts.FAILURE) }, { setResultExecutionCount++ })
+    private val monitorWithExceptionStop =
+        createMonitor(
+            { startExecutionCount++ },
+            { error(Consts.FAILURE) },
+        )
+    private val monitorWithoutException =
+        createMonitor({ startExecutionCount++ }, { setResultExecutionCount++ })
+
+    @Before
+    fun setup() {
+        startExecutionCount = 0
+        setResultExecutionCount = 0
+    }
+
+    @Test
+    fun executesSuccessfully() {
+        val rule = createRule(listOf(monitorWithoutException))
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(1)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
+    }
+
+    @Test
+    fun executesSuccessfullyMonitor2() {
+        val rule = createRule(listOf(monitorWithoutException, monitorWithoutException))
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(2)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(2)
+    }
+
+    @Test
+    fun executesWithStartFailure() {
+        val failure =
+            assertThrows<TransitionTracingFailure> {
+                val rule = createRule(listOf(monitorWithExceptionStart))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(0)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
+    }
+
+    @Test
+    fun executesStartFailureMonitor2() {
+        val failure =
+            assertThrows<TransitionTracingFailure> {
+                val rule = createRule(listOf(monitorWithExceptionStart, monitorWithoutException))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(0)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(2)
+    }
+
+    @Test
+    fun executesWithStopFailure() {
+        val failure =
+            assertThrows<TransitionTracingFailure> {
+                val rule = createRule(listOf(monitorWithExceptionStop))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(1)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(0)
+    }
+
+    @Test
+    fun executesStopFailureMonitor2() {
+        val failure =
+            assertThrows<TransitionTracingFailure> {
+                val rule = createRule(listOf(monitorWithExceptionStop, monitorWithoutException))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(2)
+        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
+    }
+
+    companion object {
+        private fun createRule(traceMonitors: List<ITransitionMonitor>): TraceMonitorRule {
+            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+            return TraceMonitorRule(
+                traceMonitors,
+                TEST_SCENARIO,
+                WindowManagerStateHelper(),
+                ResultWriter(),
+                instrumentation
+            )
+        }
+
+        private fun createMonitor(
+            onStart: () -> Unit,
+            onSetResult: (ResultWriter) -> Unit
+        ): ITransitionMonitor =
+            object : ITransitionMonitor {
+                override fun start() {
+                    onStart()
+                }
+
+                override fun stop(writer: ResultWriter) {
+                    onSetResult(writer)
+                }
+            }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRuleTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRuleTest.kt
new file mode 100644
index 0000000..738fd74
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionExecutionRuleTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.annotation.SuppressLint
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertExceptionMessage
+import android.tools.assertExceptionMessageCause
+import android.tools.assertThrows
+import android.tools.device.flicker.legacy.AbstractFlickerTestData
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.newTestResultWriter
+import androidx.test.platform.app.InstrumentationRegistry
+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
+import org.mockito.Mockito
+
+/** Tests for [TransitionExecutionRule] */
+@SuppressLint("VisibleForTests")
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TransitionExecutionRuleTest {
+    private var executed = false
+
+    private val runTransition: IFlickerTestData.() -> Unit = {
+        executed = true
+        SystemClock.sleep(100)
+    }
+    private val runCreateValidTags: IFlickerTestData.() -> Unit = {
+        createTag(VALID_TAG_1)
+        createTag(VALID_TAG_2)
+    }
+    private val runInvalidTagSpace: IFlickerTestData.() -> Unit = { createTag(INVALID_TAG_SPACE) }
+    private val runInvalidTagUnderscore: IFlickerTestData.() -> Unit = {
+        createTag(INVALID_TAG_UNDERSCORE)
+    }
+    private val throwTransitionError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
+    private val throwAssertionError: IFlickerTestData.() -> Unit = {
+        throw AssertionError(Consts.FAILURE)
+    }
+
+    @Before
+    fun setup() {
+        executed = false
+    }
+
+    @Test
+    fun runSuccessfully() {
+        val rule = createRule(listOf(runTransition))
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        Truth.assertWithMessage("Transition executed").that(executed).isTrue()
+    }
+
+    @Test
+    fun setTransitionStartAndEndTime() {
+        val writer = newTestResultWriter()
+        val rule = createRule(listOf(runTransition), writer)
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        val result = writer.write()
+        TestUtils.validateTransitionTime(result)
+    }
+
+    @Test
+    fun throwsTransitionFailure() {
+        val failure =
+            assertThrows<TransitionExecutionFailure> {
+                val rule = createRule(listOf(throwTransitionError))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        assertExceptionMessageCause(failure, Consts.FAILURE)
+        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
+    }
+
+    @Test
+    fun throwsTransitionFailureEmptyTransitions() {
+        val failure =
+            assertThrows<TransitionExecutionFailure> {
+                val rule = createRule(listOf())
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        assertExceptionMessageCause(failure, EMPTY_TRANSITIONS_ERROR)
+        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
+    }
+
+    @Test
+    fun throwsAssertionFailure() {
+        val failure =
+            assertThrows<AssertionError> {
+                val rule = createRule(listOf(throwAssertionError))
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
+        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
+    }
+
+    @Test
+    fun createsValidTags() {
+        val writer = newTestResultWriter()
+        val rule = createRule(listOf(runCreateValidTags), writer)
+        rule.apply(base = null, description = Consts.description(this)).evaluate()
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val wmStateValidTag1 =
+            reader.readWmState(VALID_TAG_1) ?: error("Couldn't parse WM state for $VALID_TAG_1")
+        val wmStateValidTag2 =
+            reader.readWmState(VALID_TAG_2) ?: error("Couldn't parse WM state for $VALID_TAG_2")
+        val layerStateValidTag1 =
+            reader.readLayersDump(VALID_TAG_1) ?: error("Couldn't parse SF state for $VALID_TAG_1")
+        val layerStateValidTag2 =
+            reader.readLayersDump(VALID_TAG_2) ?: error("Couldn't parse SF state for $VALID_TAG_2")
+
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(4)
+        Truth.assertWithMessage("WM State - $VALID_TAG_1")
+            .that(wmStateValidTag1.entries)
+            .isNotEmpty()
+        Truth.assertWithMessage("WM State - $VALID_TAG_2")
+            .that(wmStateValidTag2.entries)
+            .isNotEmpty()
+        Truth.assertWithMessage("SF State - $VALID_TAG_1")
+            .that(layerStateValidTag1.entries)
+            .isNotEmpty()
+        Truth.assertWithMessage("SF State - $VALID_TAG_2")
+            .that(layerStateValidTag2.entries)
+            .isNotEmpty()
+    }
+
+    @Test
+    fun throwErrorCreateInvalidTagWithSpace() {
+        val writer = newTestResultWriter()
+        val failure =
+            assertThrows<TransitionExecutionFailure> {
+                val rule = createRule(listOf(runInvalidTagSpace), writer)
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        assertExceptionMessage(failure, INVALID_TAG_SPACE)
+    }
+
+    @Test
+    fun throwErrorCreateInvalidTagDuplicate() {
+        val writer = newTestResultWriter()
+        val failure =
+            assertThrows<TransitionExecutionFailure> {
+                val rule = createRule(listOf(runCreateValidTags, runCreateValidTags), writer)
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        assertExceptionMessage(failure, VALID_TAG_1)
+    }
+
+    @Test
+    fun throwErrorCreateInvalidTagWithUnderscore() {
+        val writer = newTestResultWriter()
+        val failure =
+            assertThrows<TransitionExecutionFailure> {
+                val rule = createRule(listOf(runInvalidTagUnderscore), writer)
+                rule.apply(base = null, description = Consts.description(this)).evaluate()
+            }
+        assertExceptionMessage(failure, INVALID_TAG_UNDERSCORE)
+    }
+
+    companion object {
+        private const val VALID_TAG_1 = "ValidTag1"
+        private const val VALID_TAG_2 = "Valid_Tag2"
+        private const val INVALID_TAG_SPACE = "Invalid Tag"
+        private const val INVALID_TAG_UNDERSCORE = "Invalid__Tag"
+
+        private fun createRule(
+            commands: List<IFlickerTestData.() -> Unit>,
+            writer: ResultWriter = newTestResultWriter()
+        ): TransitionExecutionRule {
+            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+            val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
+            return TransitionExecutionRule(
+                mockedFlicker,
+                writer,
+                TEST_SCENARIO,
+                instrumentation,
+                commands,
+                WindowManagerStateHelper()
+            )
+        }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionRunnerTest.kt b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionRunnerTest.kt
new file mode 100644
index 0000000..2957cfe
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/flicker/legacy/runner/TransitionRunnerTest.kt
@@ -0,0 +1,230 @@
+/*
+ * 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.device.flicker.legacy.runner
+
+import android.annotation.SuppressLint
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.assertExceptionMessageCause
+import android.tools.common.io.RunStatus
+import android.tools.createMockedFlicker
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.executeShellCommand
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.io.ResultWriter
+import android.tools.device.traces.monitors.ITransitionMonitor
+import android.view.WindowManagerGlobal
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Tests for [TransitionRunner] */
+@SuppressLint("VisibleForTests")
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TransitionRunnerTest {
+    private val executionOrder = mutableListOf<String>()
+
+    private val runSetup: IFlickerTestData.() -> Unit = {
+        executionOrder.add(Consts.SETUP)
+        SystemClock.sleep(100)
+    }
+    private val runTeardown: IFlickerTestData.() -> Unit = {
+        executionOrder.add(Consts.TEARDOWN)
+        SystemClock.sleep(100)
+    }
+    private val runTransition: IFlickerTestData.() -> Unit = {
+        executionOrder.add(Consts.TRANSITION)
+        SystemClock.sleep(100)
+    }
+    private val throwError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
+
+    @Before
+    fun setup() {
+        executionOrder.clear()
+        executeShellCommand("rm -rf ${getDefaultFlickerOutputDir()}")
+    }
+
+    @After
+    fun assertTracingStopped() {
+        val windowManager = WindowManagerGlobal.getWindowManagerService()
+        Truth.assertWithMessage("Layers Trace running").that(windowManager.isLayerTracing).isFalse()
+        Truth.assertWithMessage("WM Trace running")
+            .that(windowManager.isWindowTraceEnabled)
+            .isFalse()
+    }
+
+    @Test
+    fun runsTransition() {
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
+        val dummyMonitor = dummyMonitor()
+        val mockedFlicker =
+            createMockedFlicker(
+                setup = listOf(runSetup),
+                teardown = listOf(runTeardown),
+                transitions = listOf(runTransition),
+                extraMonitor = dummyMonitor
+            )
+        val result = runner.execute(mockedFlicker, Consts.description(this))
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        validateExecutionOrder(hasTransition = true)
+        dummyMonitor.validate()
+        TestUtils.validateTransitionTime(result)
+        Truth.assertWithMessage("Run status")
+            .that(reader.runStatus)
+            .isEqualTo(RunStatus.RUN_EXECUTED)
+    }
+
+    @Test
+    fun failsWithNoTransitions() {
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
+        val dummyMonitor = dummyMonitor()
+        val mockedFlicker =
+            createMockedFlicker(
+                setup = listOf(runSetup),
+                teardown = listOf(runTeardown),
+                extraMonitor = dummyMonitor
+            )
+        val result = runner.execute(mockedFlicker, Consts.description(this))
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        validateExecutionOrder(hasTransition = false)
+        dummyMonitor.validate()
+        TestUtils.validateTransitionTime(result)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertExceptionMessageCause(result.executionError, EMPTY_TRANSITIONS_ERROR)
+    }
+
+    @Test
+    fun failsWithTransitionError() {
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
+        val dummyMonitor = dummyMonitor()
+        val mockedFlicker =
+            createMockedFlicker(
+                setup = listOf(runSetup),
+                teardown = listOf(runTeardown),
+                transitions = listOf(throwError),
+                extraMonitor = dummyMonitor
+            )
+        val result = runner.execute(mockedFlicker, Consts.description(this))
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        validateExecutionOrder(hasTransition = false)
+        dummyMonitor.validate()
+        TestUtils.validateTransitionTime(result)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
+    }
+
+    @Test
+    fun failsWithSetupErrorAndHasTraces() {
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
+        val dummyMonitor = dummyMonitor()
+        val mockedFlicker =
+            createMockedFlicker(
+                setup = listOf(runSetup, throwError),
+                teardown = listOf(runTeardown),
+                transitions = listOf(runTransition),
+                extraMonitor = dummyMonitor
+            )
+        val result = runner.execute(mockedFlicker, Consts.description(this))
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        validateExecutionOrder(hasTransition = false)
+        dummyMonitor.validate()
+        TestUtils.validateTransitionTimeIsEmpty(result)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
+    }
+
+    @Test
+    fun failsWithTeardownErrorAndHasTraces() {
+        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
+        val dummyMonitor = dummyMonitor()
+        val mockedFlicker =
+            createMockedFlicker(
+                setup = listOf(runSetup),
+                teardown = listOf(runTeardown, throwError),
+                transitions = listOf(runTransition),
+                extraMonitor = dummyMonitor
+            )
+        val result = runner.execute(mockedFlicker, Consts.description(this))
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        validateExecutionOrder(hasTransition = true)
+        dummyMonitor.validate()
+        TestUtils.validateTransitionTime(result)
+        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
+        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
+    }
+
+    private fun assertContainsOrNot(value: String, hasValue: Boolean): String? {
+        return if (hasValue) {
+            Truth.assertWithMessage("$value executed").that(executionOrder).contains(value)
+            value
+        } else {
+            Truth.assertWithMessage("$value skipped").that(executionOrder).doesNotContain(value)
+            null
+        }
+    }
+
+    private fun validateExecutionOrder(hasTransition: Boolean) {
+        val expected = mutableListOf<String>()
+        assertContainsOrNot(Consts.SETUP, hasValue = true)?.also { expected.add(it) }
+        assertContainsOrNot(Consts.TRANSITION, hasTransition)?.also { expected.add(it) }
+        assertContainsOrNot(Consts.TEARDOWN, hasValue = true)?.also { expected.add(it) }
+
+        Truth.assertWithMessage("Execution order")
+            .that(executionOrder)
+            .containsExactlyElementsIn(expected)
+            .inOrder()
+    }
+
+    private fun dummyMonitor() =
+        object : ITransitionMonitor {
+            private var startExecuted = false
+            private var setResultExecuted = false
+
+            override fun start() {
+                startExecuted = true
+            }
+
+            override fun stop(writer: ResultWriter) {
+                setResultExecuted = true
+            }
+
+            fun validate() {
+                Truth.assertWithMessage("Start executed").that(startExecuted).isTrue()
+                Truth.assertWithMessage("Set result executed").that(setResultExecuted).isTrue()
+            }
+        }
+
+    companion object {
+        private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/TimeUtilsTest.kt b/libraries/flicker/test/src/android/tools/device/traces/TimeUtilsTest.kt
new file mode 100644
index 0000000..7c0a85f
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/TimeUtilsTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.device.traces
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.device.flicker.Utils
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [Utils] formatting tests. To run this test: `atest FlickerLibTest:TimeUtilsTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TimeUtilsTest {
+    @Test
+    fun canFormatElapsedTime() {
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(0)).isEqualTo("0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(1000)).isEqualTo("1000ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MILLISECOND - 1)).isEqualTo("999999ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MILLISECOND)).isEqualTo("1ms0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(10 * MILLISECOND)).isEqualTo("10ms0ns")
+
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND - 1)).isEqualTo("999ms999999ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND)).isEqualTo("1s0ms0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND + MILLISECOND))
+            .isEqualTo("1s1ms0ns")
+
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE - 1)).isEqualTo("59s999ms999999ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE)).isEqualTo("1m0s0ms0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE + SECOND + MILLISECOND))
+            .isEqualTo("1m1s1ms0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1))
+            .isEqualTo("1m1s1ms1ns")
+
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR - 1))
+            .isEqualTo("59m59s999ms999999ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR)).isEqualTo("1h0m0s0ms0ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND))
+            .isEqualTo("1h1m1s1ms0ns")
+
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(DAY - 1))
+            .isEqualTo("23h59m59s999ms999999ns")
+        Truth.assertThat(Timestamp.formatElapsedTimestamp(DAY)).isEqualTo("1d0h0m0s0ms0ns")
+        Truth.assertThat(
+                Timestamp.formatElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND)
+            )
+            .isEqualTo("1d1h1m1s1ms0ns")
+    }
+
+    @Test
+    fun canFormatRealTime() {
+        Truth.assertThat(formatRealTimestamp(0)).isEqualTo("1970-01-01T00:00:00.000000000")
+        Truth.assertThat(
+                formatRealTimestamp(
+                    NOV_10_2022 + 22 * HOUR + 4 * MINUTE + 54 * SECOND + 186 * MILLISECOND + 123212
+                )
+            )
+            .isEqualTo("2022-11-10T22:04:54.186123212")
+        Truth.assertThat(
+                formatRealTimestamp(
+                    NOV_10_2022 + 22 * HOUR + 4 * MINUTE + 54 * SECOND + 186 * MILLISECOND + 2
+                )
+            )
+            .isEqualTo("2022-11-10T22:04:54.186000002")
+        Truth.assertThat(formatRealTimestamp(NOV_10_2022))
+            .isEqualTo("2022-11-10T00:00:00.000000000")
+        Truth.assertThat(formatRealTimestamp(NOV_10_2022 + 1))
+            .isEqualTo("2022-11-10T00:00:00.000000001")
+    }
+
+    @Test
+    fun formatToRightType() {
+        Truth.assertThat(CrossPlatform.timestamp.from(unixNanos = 1668117894186123212L).toString())
+            .startsWith("2022-11-10T22:04:54.186123212")
+        Truth.assertThat(
+                CrossPlatform.timestamp.from(elapsedNanos = 10 * DAY + 12 * HOUR).toString()
+            )
+            .startsWith("10d12h0m0s0ms0ns")
+    }
+
+    companion object {
+        private const val MILLISECOND = 1000000L
+        private const val SECOND = 1000 * MILLISECOND
+        private const val MINUTE = 60 * SECOND
+        private const val HOUR = 60 * MINUTE
+        private const val DAY = 24 * HOUR
+        private const val NOV_10_2022 = 1668038400000 * MILLISECOND
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/UtilsTest.kt b/libraries/flicker/test/src/android/tools/device/traces/UtilsTest.kt
new file mode 100644
index 0000000..0e5d384
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/UtilsTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.device.traces
+
+import android.tools.InitRule
+import android.tools.common.io.TraceType
+import android.tools.common.traces.NullableDeviceStateDump
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [android.os.traces] utils tests. */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class UtilsTest {
+    private fun getCurrState(
+        vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
+    ): Pair<ByteArray, ByteArray> {
+        return getCurrentState(*dumpTypes)
+    }
+
+    private fun getCurrStateDump(
+        vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
+    ): NullableDeviceStateDump {
+        return getCurrentStateDumpNullable(*dumpTypes, clearCacheAfterParsing = false)
+    }
+
+    @Test
+    fun canFetchCurrentDeviceState() {
+        val currState = this.getCurrState()
+        Truth.assertThat(currState.first).isNotEmpty()
+        Truth.assertThat(currState.second).isNotEmpty()
+    }
+
+    @Test
+    fun canFetchCurrentDeviceStateOnlyWm() {
+        val currStateDump = this.getCurrState(TraceType.WM_DUMP)
+        Truth.assertThat(currStateDump.first).isNotEmpty()
+        Truth.assertThat(currStateDump.second).isEmpty()
+        val currState = this.getCurrStateDump(TraceType.WM_DUMP)
+        Truth.assertThat(currState.wmState).isNotNull()
+        Truth.assertThat(currState.layerState).isNull()
+    }
+
+    @Test
+    fun canFetchCurrentDeviceStateOnlyLayers() {
+        val currStateDump = this.getCurrState(TraceType.SF_DUMP)
+        Truth.assertThat(currStateDump.first).isEmpty()
+        Truth.assertThat(currStateDump.second).isNotEmpty()
+        val currState = this.getCurrStateDump(TraceType.SF_DUMP)
+        Truth.assertThat(currState.wmState).isNull()
+        Truth.assertThat(currState.layerState).isNotNull()
+    }
+
+    @Test
+    fun canParseCurrentDeviceState() {
+        val currState = this.getCurrStateDump()
+        val wmArray = currState.wmState?.asTrace()?.entries ?: emptyArray()
+        Truth.assertThat(wmArray).asList().hasSize(1)
+        Truth.assertThat(wmArray.first().windowStates).isNotEmpty()
+        val layersArray = currState.layerState?.asTrace()?.entries ?: emptyArray()
+        Truth.assertThat(layersArray).asList().hasSize(1)
+        Truth.assertThat(layersArray.first().flattenedLayers).isNotEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/BaseResultReaderTestParseTrace.kt b/libraries/flicker/test/src/android/tools/device/traces/io/BaseResultReaderTestParseTrace.kt
new file mode 100644
index 0000000..0b2a91a
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/BaseResultReaderTestParseTrace.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.InitRule
+import android.tools.TestTraces
+import android.tools.assertExceptionMessage
+import android.tools.assertThrows
+import android.tools.common.CrossPlatform
+import android.tools.common.ITrace
+import android.tools.common.Timestamp
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Base class for [ResultReader] tests parsing traces */
+abstract class BaseResultReaderTestParseTrace {
+    protected abstract val assetFile: File
+    protected abstract val traceName: String
+    protected abstract val startTimeTrace: Timestamp
+    protected abstract val endTimeTrace: Timestamp
+    protected abstract val validSliceTime: Timestamp
+    protected abstract val invalidSliceTime: Timestamp
+    protected abstract val traceType: TraceType
+    protected abstract val expectedSlicedTraceSize: Int
+    protected open val invalidSizeMessage: String
+        get() = "$traceName contained 0 entries, expected at least 2"
+
+    protected abstract fun doParse(reader: ResultReader): ITrace<*>?
+    protected abstract fun getTime(traceTime: Timestamp): Long
+
+    protected open fun setupWriter(writer: ResultWriter): ResultWriter {
+        writer.addTraceResult(traceType, assetFile)
+        return writer
+    }
+
+    @Before
+    fun setup() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+    }
+
+    @Test
+    fun readTrace() {
+        val writer = setupWriter(newTestResultWriter())
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val trace = doParse(reader) ?: error("$traceName not built")
+
+        Truth.assertWithMessage(traceName).that(trace.entries).asList().isNotEmpty()
+        Truth.assertWithMessage("$traceName start")
+            .that(getTime(trace.entries.first().timestamp))
+            .isEqualTo(getTime(startTimeTrace))
+        Truth.assertWithMessage("$traceName end")
+            .that(getTime(trace.entries.last().timestamp))
+            .isEqualTo(getTime(endTimeTrace))
+    }
+
+    @Test
+    fun readTraceNullWhenDoesNotExist() {
+        val writer = newTestResultWriter()
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val trace = doParse(reader)
+
+        Truth.assertWithMessage(traceName).that(trace).isNull()
+    }
+
+    @Test
+    fun readTraceAndSliceTraceByTimestamp() {
+        val result =
+            setupWriter(newTestResultWriter())
+                .setTransitionStartTime(startTimeTrace)
+                .setTransitionEndTime(validSliceTime)
+                .write()
+        val reader = ResultReader(result, TestTraces.TEST_TRACE_CONFIG)
+        val trace = doParse(reader) ?: error("$traceName not built")
+
+        Truth.assertWithMessage(traceName)
+            .that(trace.entries)
+            .asList()
+            .hasSize(expectedSlicedTraceSize)
+        Truth.assertWithMessage("$traceName start")
+            .that(getTime(trace.entries.first().timestamp))
+            .isEqualTo(getTime(startTimeTrace))
+    }
+
+    @Test
+    fun readTraceAndSliceTraceByTimestampAndFailInvalidSize() {
+        val result =
+            setupWriter(newTestResultWriter())
+                .setTransitionEndTime(CrossPlatform.timestamp.min())
+                .write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val exception =
+            assertThrows<IllegalArgumentException> {
+                doParse(reader) ?: error("$traceName not built")
+            }
+        assertExceptionMessage(exception, invalidSizeMessage)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ParsedTracesReader.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ParsedTracesReader.kt
new file mode 100644
index 0000000..6541d07
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ParsedTracesReader.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.common.Timestamp
+import android.tools.common.io.IReader
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.common.traces.events.CujTrace
+import android.tools.common.traces.events.EventLog
+import android.tools.common.traces.surfaceflinger.LayersTrace
+import android.tools.common.traces.surfaceflinger.TransactionsTrace
+import android.tools.common.traces.wm.TransitionsTrace
+import android.tools.common.traces.wm.WindowManagerTrace
+
+/** Reads parsed traces from in memory objects */
+class ParsedTracesReader(
+    private val wmTrace: WindowManagerTrace? = null,
+    private val layersTrace: LayersTrace? = null,
+    private val transitionsTrace: TransitionsTrace? = null,
+    private val transactionsTrace: TransactionsTrace? = null,
+    private val eventLog: EventLog? = null
+) : IReader {
+    override val artifactPath = ""
+    override val runStatus = RunStatus.UNDEFINED
+    override val executionError = null
+
+    override fun readLayersTrace(): LayersTrace? = layersTrace
+
+    override fun readTransactionsTrace(): TransactionsTrace? = transactionsTrace
+
+    override fun readTransitionsTrace(): TransitionsTrace? = transitionsTrace
+
+    override fun readWmTrace(): WindowManagerTrace? = wmTrace
+
+    override fun readEventLogTrace(): EventLog? = eventLog
+
+    override fun readCujTrace(): CujTrace? = eventLog?.cujTrace
+
+    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ParsedTracesReader {
+        return ParsedTracesReader(
+            wmTrace?.slice(startTimestamp, endTimestamp),
+            layersTrace?.slice(startTimestamp, endTimestamp),
+            transitionsTrace?.slice(startTimestamp, endTimestamp),
+            transactionsTrace?.slice(startTimestamp, endTimestamp),
+            eventLog?.slice(startTimestamp, endTimestamp)
+        )
+    }
+
+    override fun readLayersDump(tag: String): LayersTrace? {
+        error("Trace type not available")
+    }
+
+    override fun readWmState(tag: String): WindowManagerTrace? {
+        error("Trace type not available")
+    }
+
+    override fun readBytes(traceType: TraceType, tag: String): ByteArray? {
+        error("Feature not available")
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTest.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTest.kt
new file mode 100644
index 0000000..74c2512
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.InitRule
+import android.tools.assertThrows
+import android.tools.common.io.RunStatus
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import java.io.FileNotFoundException
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Tests for [ResultReader] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ResultReaderTest {
+    @Before
+    fun setup() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+    }
+
+    @Test
+    fun failFileNotFound() {
+        val data = newTestResultWriter().write()
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        val reader = ResultReader(data, DEFAULT_TRACE_CONFIG)
+        assertThrows<FileNotFoundException> {
+            reader.readTransitionsTrace() ?: error("Should have failed")
+        }
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseEventLog.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseEventLog.kt
new file mode 100644
index 0000000..4ff5bbb
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseEventLog.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+import org.junit.FixMethodOrder
+import org.junit.runners.MethodSorters
+
+/** Tests for [ResultReader] parsing event log */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ResultReaderTestParseEventLog : BaseResultReaderTestParseTrace() {
+    override val assetFile = TestTraces.EventLog.FILE
+    override val traceName = "Event Log"
+    override val startTimeTrace = TestTraces.EventLog.START_TIME
+    override val endTimeTrace = TestTraces.EventLog.END_TIME
+    override val validSliceTime = TestTraces.EventLog.SLICE_TIME
+    override val invalidSliceTime = startTimeTrace
+    override val traceType = TraceType.EVENT_LOG
+    override val expectedSlicedTraceSize = 125
+    override val invalidSizeMessage: String = "'to' needs to be greater than 'from'"
+
+    override fun doParse(reader: ResultReader) = reader.readEventLogTrace()
+    override fun getTime(traceTime: Timestamp) = traceTime.unixNanos
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseLayers.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseLayers.kt
new file mode 100644
index 0000000..436f65d
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseLayers.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+
+/** Tests for [ResultReader] parsing [TraceType.SF] */
+class ResultReaderTestParseLayers : BaseResultReaderTestParseTrace() {
+    override val assetFile = TestTraces.LayerTrace.FILE
+    override val traceName = "Layers trace"
+    override val startTimeTrace = TestTraces.LayerTrace.START_TIME
+    override val endTimeTrace = TestTraces.LayerTrace.END_TIME
+    override val validSliceTime = TestTraces.LayerTrace.SLICE_TIME
+    override val invalidSliceTime = startTimeTrace
+    override val traceType = TraceType.SF
+    override val expectedSlicedTraceSize = 2
+
+    override fun doParse(reader: ResultReader) = reader.readLayersTrace()
+    override fun getTime(traceTime: Timestamp) = traceTime.systemUptimeNanos
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransactions.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransactions.kt
new file mode 100644
index 0000000..16be799
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransactions.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+
+/** Tests for [ResultReader] parsing [TraceType.TRANSACTION] */
+class ResultReaderTestParseTransactions : BaseResultReaderTestParseTrace() {
+    override val assetFile = TestTraces.TransactionTrace.FILE
+    override val traceName = "Transactions trace"
+    override val startTimeTrace = TestTraces.TransactionTrace.START_TIME
+    override val endTimeTrace = TestTraces.TransactionTrace.END_TIME
+    override val validSliceTime = TestTraces.TransactionTrace.VALID_SLICE_TIME
+    override val invalidSliceTime = TestTraces.TransactionTrace.INVALID_SLICE_TIME
+    override val traceType = TraceType.TRANSACTION
+    override val invalidSizeMessage = "Transactions trace cannot be empty"
+    override val expectedSlicedTraceSize = 2
+
+    override fun doParse(reader: ResultReader) = reader.readTransactionsTrace()
+    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransitions.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransitions.kt
new file mode 100644
index 0000000..8bbfe30
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseTransitions.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+import android.tools.readAssetAsFile
+
+/** Tests for [ResultReader] parsing [TraceType.TRANSITION] */
+class ResultReaderTestParseTransitions : BaseResultReaderTestParseTrace() {
+    override val assetFile = TestTraces.TransitionTrace.FILE
+    override val traceName = "Transitions trace"
+    override val startTimeTrace = TestTraces.TransitionTrace.START_TIME
+    override val endTimeTrace = TestTraces.TransitionTrace.END_TIME
+    override val validSliceTime = TestTraces.TransitionTrace.VALID_SLICE_TIME
+    override val invalidSliceTime = TestTraces.TransitionTrace.INVALID_SLICE_TIME
+    override val traceType = TraceType.TRANSITION
+    override val invalidSizeMessage = "Transitions trace cannot be empty"
+    override val expectedSlicedTraceSize = 1
+
+    override fun doParse(reader: ResultReader) = reader.readTransitionsTrace()
+    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
+    override fun setupWriter(writer: ResultWriter): ResultWriter {
+        return super.setupWriter(writer).also {
+            val trace = readAssetAsFile("transition_trace.winscope")
+            it.addTraceResult(TraceType.TRANSITION, trace)
+        }
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseWM.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseWM.kt
new file mode 100644
index 0000000..58c584a
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultReaderTestParseWM.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.device.traces.io
+
+import android.tools.TestTraces
+import android.tools.common.Timestamp
+import android.tools.common.io.TraceType
+
+/** Tests for [ResultReader] parsing [TraceType.WM] */
+class ResultReaderTestParseWM : BaseResultReaderTestParseTrace() {
+    override val assetFile = TestTraces.WMTrace.FILE
+    override val traceName = "WM trace"
+    override val startTimeTrace = TestTraces.WMTrace.START_TIME
+    override val endTimeTrace = TestTraces.WMTrace.END_TIME
+    override val validSliceTime = TestTraces.WMTrace.SLICE_TIME
+    override val invalidSliceTime = startTimeTrace
+    override val traceType = TraceType.WM
+    override val expectedSlicedTraceSize: Int = 2
+
+    override fun doParse(reader: ResultReader) = reader.readWmTrace()
+    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/io/ResultWriterTest.kt b/libraries/flicker/test/src/android/tools/device/traces/io/ResultWriterTest.kt
new file mode 100644
index 0000000..871112b
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/io/ResultWriterTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.device.traces.io
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.TEST_SCENARIO
+import android.tools.TestTraces
+import android.tools.assertExceptionMessage
+import android.tools.assertThrows
+import android.tools.common.CrossPlatform
+import android.tools.common.ScenarioBuilder
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import com.google.common.truth.Truth
+import java.io.File
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Tests for [ResultWriter] */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressLint("VisibleForTests")
+class ResultWriterTest {
+    @Test
+    fun cannotWriteFileWithoutScenario() {
+        val exception =
+            assertThrows<IllegalArgumentException> {
+                val writer =
+                    newTestResultWriter().forScenario(ScenarioBuilder().createEmptyScenario())
+                writer.write()
+            }
+
+        assertExceptionMessage(exception, "Scenario shouldn't be empty")
+    }
+
+    @Test
+    fun writesEmptyFile() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        val writer = newTestResultWriter()
+        val result = writer.write()
+        val path = result.artifact
+        Truth.assertWithMessage("File exists").that(path.exists()).isTrue()
+        Truth.assertWithMessage("Transition start time")
+            .that(result.transitionTimeRange.start)
+            .isEqualTo(CrossPlatform.timestamp.min())
+        Truth.assertWithMessage("Transition end time")
+            .that(result.transitionTimeRange.end)
+            .isEqualTo(CrossPlatform.timestamp.max())
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(0)
+    }
+
+    @Test
+    fun writesUndefinedFile() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        val writer =
+            ResultWriter().forScenario(TEST_SCENARIO).withOutputDir(getDefaultFlickerOutputDir())
+        val result = writer.write()
+        val path = result.artifact
+        validateFileName(path, RunStatus.UNDEFINED)
+    }
+
+    @Test
+    fun writesRunCompleteFile() {
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        val writer = newTestResultWriter().setRunComplete()
+        val result = writer.write()
+        val path = result.artifact
+        validateFileName(path, RunStatus.RUN_EXECUTED)
+    }
+
+    @Test
+    fun writesRunFailureFile() {
+        outputFileName(RunStatus.RUN_FAILED).deleteIfExists()
+        val writer = newTestResultWriter().setRunFailed(EXPECTED_FAILURE)
+        val result = writer.write()
+        val path = result.artifact
+        validateFileName(path, RunStatus.RUN_FAILED)
+        Truth.assertWithMessage("Expected assertion")
+            .that(result.executionError)
+            .isEqualTo(EXPECTED_FAILURE)
+    }
+
+    @Test
+    fun writesTransitionTime() {
+        val writer =
+            newTestResultWriter()
+                .setTransitionStartTime(TestTraces.TIME_5)
+                .setTransitionEndTime(TestTraces.TIME_10)
+
+        val result = writer.write()
+        Truth.assertWithMessage("Transition start time")
+            .that(result.transitionTimeRange.start)
+            .isEqualTo(TestTraces.TIME_5)
+        Truth.assertWithMessage("Transition end time")
+            .that(result.transitionTimeRange.end)
+            .isEqualTo(TestTraces.TIME_10)
+    }
+
+    @Test
+    fun writeWMTrace() {
+        val writer = newTestResultWriter().addTraceResult(TraceType.WM, TestTraces.WMTrace.FILE)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.WM))
+            .isTrue()
+    }
+
+    @Test
+    fun writeLayersTrace() {
+        val writer = newTestResultWriter().addTraceResult(TraceType.SF, TestTraces.LayerTrace.FILE)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.SF))
+            .isTrue()
+    }
+
+    @Test
+    fun writeTransactionTrace() {
+        val writer =
+            newTestResultWriter()
+                .addTraceResult(TraceType.TRANSACTION, TestTraces.TransactionTrace.FILE)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.TRANSACTION))
+            .isTrue()
+    }
+
+    @Test
+    fun writeTransitionTrace() {
+        val writer =
+            newTestResultWriter()
+                .addTraceResult(TraceType.TRANSITION, TestTraces.TransitionTrace.FILE)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.TRANSITION))
+            .isTrue()
+    }
+
+    @Test
+    fun writeAllTraces() {
+        val writer =
+            newTestResultWriter()
+                .addTraceResult(TraceType.WM, TestTraces.WMTrace.FILE)
+                .addTraceResult(TraceType.SF, TestTraces.LayerTrace.FILE)
+                .addTraceResult(TraceType.TRANSITION, TestTraces.TransactionTrace.FILE)
+                .addTraceResult(TraceType.TRANSACTION, TestTraces.TransitionTrace.FILE)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(4)
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.WM))
+            .isTrue()
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.WM))
+            .isTrue()
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.TRANSITION))
+            .isTrue()
+        Truth.assertWithMessage("Has file with type")
+            .that(reader.hasTraceFile(TraceType.TRANSACTION))
+            .isTrue()
+    }
+
+    companion object {
+        private val EXPECTED_FAILURE = IllegalArgumentException("Expected test exception")
+
+        private fun validateFileName(filePath: File, status: RunStatus) {
+            Truth.assertWithMessage("File name contains run status")
+                .that(filePath.name)
+                .contains(status.prefix)
+        }
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/monitors/ScreenRecorderTest.kt b/libraries/flicker/test/src/android/tools/device/traces/monitors/ScreenRecorderTest.kt
new file mode 100644
index 0000000..d48c1f3
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/monitors/ScreenRecorderTest.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.device.traces.monitors
+
+import android.app.Instrumentation
+import android.media.MediaCodec
+import android.media.MediaFormat
+import android.media.MediaParser
+import android.os.SystemClock
+import android.tools.InitRule
+import android.tools.common.io.TraceType
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.executeShellCommand
+import android.tools.device.traces.getDefaultFlickerOutputDir
+import android.tools.device.traces.io.ResultReader
+import android.tools.newTestResultWriter
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/** Contains [ScreenRecorder] tests. To run this test: `atest FlickerLibTest:ScreenRecorderTest` */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ScreenRecorderTest {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val mScreenRecorder =
+        ScreenRecorder(instrumentation.targetContext, getDefaultFlickerOutputDir())
+
+    @Before
+    fun clearOutputDir() {
+        executeShellCommand("rm -rf ${getDefaultFlickerOutputDir()}")
+    }
+
+    @After
+    fun teardown() {
+        if (mScreenRecorder.isEnabled) {
+            mScreenRecorder.stop(newTestResultWriter())
+        }
+    }
+
+    @Test
+    fun videoIsRecorded() {
+        mScreenRecorder.start()
+        val device = UiDevice.getInstance(instrumentation)
+        device.wakeUp()
+        SystemClock.sleep(500)
+        device.pressHome()
+        var remainingTime = TIMEOUT
+        do {
+            remainingTime -= 100
+            SystemClock.sleep(STEP)
+        } while (!mScreenRecorder.isFrameRecorded && remainingTime > 0)
+        val writer = newTestResultWriter()
+        mScreenRecorder.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Screen recording file exists")
+            .that(reader.hasTraceFile(TraceType.SCREEN_RECORDING))
+            .isTrue()
+
+        val outputData =
+            reader.readBytes(TraceType.SCREEN_RECORDING) ?: error("Screen recording not found")
+        val (metadataTrack, videoTrack) = parseScreenRecording(outputData)
+
+        Truth.assertThat(metadataTrack.isEmpty()).isFalse()
+        Truth.assertThat(videoTrack.isEmpty()).isFalse()
+
+        val actualMagicString = metadataTrack.copyOfRange(0, WINSCOPE_MAGIC_STRING.size)
+        Truth.assertThat(actualMagicString).isEqualTo(WINSCOPE_MAGIC_STRING)
+    }
+
+    private fun parseScreenRecording(data: ByteArray): Pair<ByteArray, ByteArray> {
+        val inputReader = ScreenRecorderSeekableInputReader(data)
+        val outputConsumer = ScreenRecorderOutputConsumer()
+        val mediaParser = MediaParser.create(outputConsumer)
+
+        while (mediaParser.advance(inputReader)) {
+            // no op
+        }
+        mediaParser.release()
+
+        return Pair(outputConsumer.getMetadataTrack(), outputConsumer.getVideoTrack())
+    }
+
+    companion object {
+        private const val TIMEOUT = 10000L
+        private const val STEP = 100L
+        private val WINSCOPE_MAGIC_STRING =
+            byteArrayOf(
+                0x23,
+                0x56,
+                0x56,
+                0x31,
+                0x4e,
+                0x53,
+                0x43,
+                0x30,
+                0x50,
+                0x45,
+                0x54,
+                0x31,
+                0x4d,
+                0x45,
+                0x32,
+                0x23
+            ) // "#VV1NSC0PET1ME2#"
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+
+    internal class ScreenRecorderSeekableInputReader(private val bytes: ByteArray) :
+        MediaParser.SeekableInputReader {
+        private var position = 0L
+
+        override fun getPosition(): Long = position
+
+        override fun getLength(): Long = bytes.size.toLong() - position
+
+        override fun seekToPosition(position: Long) {
+            this.position = position
+        }
+
+        override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {
+            if (position >= bytes.size) {
+                return -1
+            }
+
+            val actualLength = kotlin.math.min(readLength.toLong(), bytes.size - position)
+            for (i in 0 until actualLength) {
+                buffer[(offset + i).toInt()] = bytes[(position + i).toInt()]
+            }
+
+            position += actualLength
+
+            return actualLength.toInt()
+        }
+    }
+
+    internal class ScreenRecorderOutputConsumer : MediaParser.OutputConsumer {
+        private var videoTrack = ArrayList<Byte>()
+        private var metadataTrack = ArrayList<Byte>()
+        private var videoTrackIndex = -1
+        private var metadataTrackIndex = -1
+        private val auxBuffer = ByteArray(4 * 1024)
+
+        fun getVideoTrack(): ByteArray {
+            return videoTrack.toByteArray()
+        }
+
+        fun getMetadataTrack(): ByteArray {
+            return metadataTrack.toByteArray()
+        }
+
+        override fun onSeekMapFound(seekMap: MediaParser.SeekMap) {
+            // do nothing
+        }
+
+        override fun onTrackCountFound(numberOfTracks: Int) {
+            Truth.assertThat(numberOfTracks).isEqualTo(2)
+        }
+
+        override fun onTrackDataFound(i: Int, trackData: MediaParser.TrackData) {
+            if (
+                videoTrackIndex == -1 &&
+                    trackData.mediaFormat.getString(MediaFormat.KEY_MIME, "").startsWith("video/")
+            ) {
+                videoTrackIndex = i
+            }
+
+            if (
+                metadataTrackIndex == -1 &&
+                    trackData.mediaFormat.getString(MediaFormat.KEY_MIME, "") ==
+                        "application/octet-stream"
+            ) {
+                metadataTrackIndex = i
+            }
+        }
+
+        override fun onSampleDataFound(trackIndex: Int, inputReader: MediaParser.InputReader) {
+            when (trackIndex) {
+                videoTrackIndex -> processSampleData(inputReader, videoTrack)
+                metadataTrackIndex -> processSampleData(inputReader, metadataTrack)
+                else -> throw RuntimeException("unexpected track index: $trackIndex")
+            }
+        }
+
+        override fun onSampleCompleted(
+            trackIndex: Int,
+            timeMicros: Long,
+            flags: Int,
+            size: Int,
+            offset: Int,
+            cryptoData: MediaCodec.CryptoInfo?
+        ) {
+            // do nothing
+        }
+
+        private fun processSampleData(
+            inputReader: MediaParser.InputReader,
+            buffer: ArrayList<Byte>
+        ) {
+            while (inputReader.length > 0) {
+                val requestLength = kotlin.math.min(inputReader.length, auxBuffer.size.toLong())
+                val actualLength = inputReader.read(auxBuffer, 0, requestLength.toInt())
+                if (actualLength == -1) {
+                    break
+                }
+
+                for (i in 0 until actualLength) {
+                    buffer.add(auxBuffer[i])
+                }
+            }
+        }
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/monitors/TraceMonitorTest.kt b/libraries/flicker/test/src/android/tools/device/traces/monitors/TraceMonitorTest.kt
new file mode 100644
index 0000000..614bbb3
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/monitors/TraceMonitorTest.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.device.traces.monitors
+
+import android.app.Instrumentation
+import android.support.test.uiautomator.UiDevice
+import android.tools.InitRule
+import android.tools.common.io.RunStatus
+import android.tools.common.io.TraceType
+import android.tools.common.traces.DeviceTraceDump
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.deleteIfExists
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.parsers.DeviceDumpParser
+import android.tools.newTestResultWriter
+import android.tools.outputFileName
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+abstract class TraceMonitorTest<T : TraceMonitor> {
+    abstract fun getMonitor(): T
+    abstract fun assertTrace(traceData: ByteArray)
+    abstract val traceType: TraceType
+
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    protected val device: UiDevice = UiDevice.getInstance(instrumentation)
+    private val traceMonitor by lazy { getMonitor() }
+
+    @Before
+    fun before() {
+        Truth.assertWithMessage("Trace already enabled before starting test")
+            .that(traceMonitor.isEnabled)
+            .isFalse()
+    }
+
+    @After
+    fun teardown() {
+        device.pressHome()
+        if (traceMonitor.isEnabled) {
+            traceMonitor.stop(newTestResultWriter())
+        }
+        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
+        Truth.assertWithMessage("Failed to disable trace at end of test")
+            .that(traceMonitor.isEnabled)
+            .isFalse()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun canStartTrace() {
+        traceMonitor.start()
+        Truth.assertThat(traceMonitor.isEnabled).isTrue()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun canStopTrace() {
+        traceMonitor.start()
+        Truth.assertThat(traceMonitor.isEnabled).isTrue()
+        traceMonitor.stop(newTestResultWriter())
+        Truth.assertThat(traceMonitor.isEnabled).isFalse()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun captureTrace() {
+        traceMonitor.start()
+        val writer = newTestResultWriter()
+        traceMonitor.stop(writer)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        Truth.assertWithMessage("Trace file exists ${traceMonitor.traceType}")
+            .that(reader.hasTraceFile(traceMonitor.traceType))
+            .isTrue()
+
+        val trace =
+            reader.readBytes(traceMonitor.traceType)
+                ?: error("Missing trace file ${traceMonitor.traceType}")
+        Truth.assertWithMessage("Trace file has data").that(trace.size).isGreaterThan(0)
+        assertTrace(trace)
+    }
+
+    private fun validateTrace(dump: DeviceTraceDump) {
+        Truth.assertWithMessage("Could not obtain SF trace")
+            .that(dump.layersTrace?.entries ?: emptyArray())
+            .asList()
+            .isNotEmpty()
+        Truth.assertWithMessage("Could not obtain WM trace")
+            .that(dump.wmTrace?.entries ?: emptyArray())
+            .asList()
+            .isNotEmpty()
+    }
+
+    @Test
+    fun withTracing() {
+        val trace = withTracing {
+            device.pressHome()
+            device.pressRecentApps()
+        }
+
+        this.validateTrace(trace)
+    }
+
+    @Test
+    fun recordTraces() {
+        val trace = recordTraces {
+            device.pressHome()
+            device.pressRecentApps()
+        }
+
+        val dump = DeviceDumpParser.fromTrace(trace.first, trace.second, clearCache = true)
+        this.validateTrace(dump)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/monitors/events/EventLogMonitorTest.kt b/libraries/flicker/test/src/android/tools/device/traces/monitors/events/EventLogMonitorTest.kt
new file mode 100644
index 0000000..0f1cbee
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/monitors/events/EventLogMonitorTest.kt
@@ -0,0 +1,419 @@
+/*
+ * 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.device.traces.monitors.events
+
+import android.os.SystemClock
+import android.tools.InitRule
+import android.tools.common.io.TraceType
+import android.tools.common.traces.events.CujEvent
+import android.tools.common.traces.events.CujType
+import android.tools.common.traces.events.EventLog.Companion.MAGIC_NUMBER
+import android.tools.common.traces.events.FocusEvent
+import android.tools.device.traces.DEFAULT_TRACE_CONFIG
+import android.tools.device.traces.io.ResultReader
+import android.tools.device.traces.monitors.TraceMonitorTest
+import android.tools.device.traces.now
+import android.tools.newTestResultWriter
+import android.util.EventLog
+import com.android.internal.jank.EventLogTags
+import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Contains [EventLogMonitor] tests. To run this test: {@code atest
+ * FlickerLibTest:EventLogMonitorTest}
+ */
+class EventLogMonitorTest : TraceMonitorTest<EventLogMonitor>() {
+    override val traceType = TraceType.EVENT_LOG
+    override fun getMonitor(): EventLogMonitor = EventLogMonitor()
+
+    override fun assertTrace(traceData: ByteArray) {
+        Truth.assertThat(traceData.size).isAtLeast(MAGIC_NUMBER.toByteArray().size)
+        Truth.assertThat(traceData.slice(0 until MAGIC_NUMBER.toByteArray().size))
+            .isEqualTo(MAGIC_NUMBER.toByteArray().asList())
+    }
+
+    @Test
+    fun canCaptureFocusEventLogs() {
+        val monitor = EventLogMonitor()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 111 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 222 com.google.android.apps.nexuslauncher/" +
+                "com.google.android.apps.nexuslauncher.NexusLauncherActivity (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 333 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        monitor.start()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 4749f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        val writer = newTestResultWriter()
+        monitor.stop(writer)
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 2aa30cd com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace()
+        requireNotNull(eventLog) { "EventLog was null" }
+
+        assertEquals(2, eventLog.focusEvents.size)
+        assertEquals(
+            "4749f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[0].window
+        )
+        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
+        assertEquals(
+            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[1].window
+        )
+        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
+        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
+        assertEquals(eventLog.focusEvents[0].reason, "test")
+    }
+
+    @Test
+    fun onlyCapturesLastTransition() {
+        val monitor = EventLogMonitor()
+        monitor.start()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 11111 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 22222 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        monitor.stop(newTestResultWriter())
+
+        monitor.start()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 479f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        val writer = newTestResultWriter()
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace()
+        requireNotNull(eventLog) { "EventLog was null" }
+
+        assertEquals(2, eventLog.focusEvents.size)
+        assertEquals(
+            "479f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[0].window
+        )
+        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
+        assertEquals(
+            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[1].window
+        )
+        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
+        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
+    }
+
+    @Test
+    fun ignoreFocusRequestLogs() {
+        val monitor = EventLogMonitor()
+        monitor.start()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 4749f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus request 111 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        val writer = newTestResultWriter()
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace()
+        requireNotNull(eventLog) { "EventLog was null" }
+
+        assertEquals(2, eventLog.focusEvents.size)
+        assertEquals(
+            "4749f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[0].window
+        )
+        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
+        assertEquals(
+            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
+            eventLog.focusEvents[1].window
+        )
+        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
+        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
+        assertEquals(eventLog.focusEvents[0].reason, "test")
+    }
+
+    @Test
+    fun savesEventLogsToFile() {
+        val monitor = EventLogMonitor()
+        monitor.start()
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 4749f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus request 111 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+        val writer = newTestResultWriter()
+        monitor.stop(writer)
+        val result = writer.write()
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+
+        Truth.assertWithMessage("Trace not found")
+            .that(reader.hasTraceFile(TraceType.EVENT_LOG))
+            .isTrue()
+    }
+
+    @Test
+    fun cropsEventsFromBeforeMonitorStart() {
+        val monitor = EventLogMonitor()
+
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 4749f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+
+        monitor.start()
+
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+
+        val writer = newTestResultWriter()
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
+
+        Truth.assertThat(eventLog.focusEvents).hasLength(1)
+        Truth.assertThat(eventLog.focusEvents.first().type).isEqualTo(FocusEvent.Type.GAINED)
+    }
+
+    @Test
+    fun cropsEventsOutsideOfTransitionTimes() {
+        val monitor = EventLogMonitor()
+        val writer = newTestResultWriter()
+        monitor.start()
+
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus leaving 4749f88 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+
+        writer.setTransitionStartTime(now())
+
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+
+        writer.setTransitionEndTime(now())
+
+        EventLog.writeEvent(
+            INPUT_FOCUS_TAG /* input_focus */,
+            "Focus entering 7c01447 com.android.phone/" +
+                "com.android.phone.settings.fdn.FdnSetting (server)",
+            "reason=test"
+        )
+
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
+
+        Truth.assertThat(eventLog.focusEvents).hasLength(1)
+        Truth.assertThat(eventLog.focusEvents.first().hasFocus()).isTrue()
+    }
+
+    @Test
+    fun canCaptureCujEvents() {
+        val monitor = EventLogMonitor()
+        val writer = newTestResultWriter()
+        monitor.start()
+        EventLogTags.writeJankCujEventsBeginRequest(
+            CujType.CUJ_NOTIFICATION_APP_START.ordinal,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        EventLogTags.writeJankCujEventsEndRequest(
+            CujType.CUJ_NOTIFICATION_APP_START.ordinal,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
+
+        assertEquals(2, eventLog.cujEvents.size)
+    }
+
+    @Test
+    fun collectsCujEventData() {
+        val monitor = EventLogMonitor()
+        val writer = newTestResultWriter()
+        monitor.start()
+        EventLogTags.writeJankCujEventsBeginRequest(
+            CujType.CUJ_LAUNCHER_QUICK_SWITCH.ordinal,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        EventLogTags.writeJankCujEventsEndRequest(
+            CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL.ordinal,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        EventLogTags.writeJankCujEventsCancelRequest(
+            CujType.CUJ_LOCKSCREEN_LAUNCH_CAMERA.ordinal,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
+
+        assertEquals(3, eventLog.cujEvents.size)
+
+        Truth.assertThat(eventLog.cujEvents[0].type).isEqualTo(CujEvent.Companion.Type.START)
+        Truth.assertThat(eventLog.cujEvents[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_QUICK_SWITCH)
+
+        Truth.assertThat(eventLog.cujEvents[1].type).isEqualTo(CujEvent.Companion.Type.END)
+        Truth.assertThat(eventLog.cujEvents[1].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
+
+        Truth.assertThat(eventLog.cujEvents[2].type).isEqualTo(CujEvent.Companion.Type.CANCEL)
+        Truth.assertThat(eventLog.cujEvents[2].cuj).isEqualTo(CujType.CUJ_LOCKSCREEN_LAUNCH_CAMERA)
+    }
+
+    @Test
+    fun canParseHandleUnknownCujTypes() {
+        val unknownCujId = Int.MAX_VALUE
+        val monitor = EventLogMonitor()
+        val writer = newTestResultWriter()
+        monitor.start()
+        EventLogTags.writeJankCujEventsBeginRequest(
+            unknownCujId,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        EventLogTags.writeJankCujEventsEndRequest(
+            unknownCujId,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        EventLogTags.writeJankCujEventsCancelRequest(
+            unknownCujId,
+            SystemClock.elapsedRealtimeNanos(),
+            SystemClock.uptimeNanos()
+        )
+        monitor.stop(writer)
+        val result = writer.write()
+
+        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
+        val eventLog = reader.readEventLogTrace()
+        requireNotNull(eventLog) { "EventLog should have been created" }
+
+        assertEquals(3, eventLog.cujEvents.size)
+        Truth.assertThat(eventLog.cujEvents[0].cuj).isEqualTo(CujType.UNKNOWN)
+        Truth.assertThat(eventLog.cujEvents[1].cuj).isEqualTo(CujType.UNKNOWN)
+        Truth.assertThat(eventLog.cujEvents[2].cuj).isEqualTo(CujType.UNKNOWN)
+    }
+
+    private companion object {
+        const val INPUT_FOCUS_TAG = 62001
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitorTest.kt b/libraries/flicker/test/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitorTest.kt
new file mode 100644
index 0000000..7a2189a
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/monitors/surfaceflinger/LayersTraceMonitorTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.device.traces.monitors.surfaceflinger
+
+import android.surfaceflinger.Layerstrace
+import android.tools.InitRule
+import android.tools.common.io.TraceType
+import android.tools.device.traces.monitors.TraceMonitorTest
+import android.tools.device.traces.monitors.withSFTracing
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [LayersTraceMonitor] tests. To run this test: `atest
+ * FlickerLibTest:LayersTraceMonitorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LayersTraceMonitorTest : TraceMonitorTest<LayersTraceMonitor>() {
+    override val traceType = TraceType.SF
+    override fun getMonitor() = LayersTraceMonitor()
+
+    override fun assertTrace(traceData: ByteArray) {
+        val trace = Layerstrace.LayersTraceFileProto.parseFrom(traceData)
+        Truth.assertThat(trace.magicNumber)
+            .isEqualTo(
+                Layerstrace.LayersTraceFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl
+                    32 or
+                    Layerstrace.LayersTraceFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+            )
+    }
+
+    @Test
+    fun withSFTracingTest() {
+        val trace = withSFTracing {
+            device.pressHome()
+            device.pressRecentApps()
+        }
+
+        Truth.assertWithMessage("Could not obtain SF trace").that(trace.entries).isNotEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitorTest.kt b/libraries/flicker/test/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitorTest.kt
new file mode 100644
index 0000000..8b67f00
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/monitors/wm/WindowManagerTraceMonitorTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.device.traces.monitors.wm
+
+import android.tools.InitRule
+import android.tools.common.io.TraceType
+import android.tools.device.traces.monitors.TraceMonitorTest
+import android.tools.device.traces.monitors.withWMTracing
+import com.android.server.wm.nano.WindowManagerTraceFileProto
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+
+/**
+ * Contains [WindowManagerTraceMonitor] tests. To run this test: `atest
+ * FlickerLibTest:LayersTraceMonitorTest`
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class WindowManagerTraceMonitorTest : TraceMonitorTest<WindowManagerTraceMonitor>() {
+    override val traceType = TraceType.WM
+    override fun getMonitor() = WindowManagerTraceMonitor()
+
+    override fun assertTrace(traceData: ByteArray) {
+        val trace = WindowManagerTraceFileProto.parseFrom(traceData)
+        Truth.assertThat(trace.magicNumber)
+            .isEqualTo(
+                WindowManagerTraceFileProto.MAGIC_NUMBER_H.toLong() shl
+                    32 or
+                    WindowManagerTraceFileProto.MAGIC_NUMBER_L.toLong()
+            )
+    }
+
+    @Test
+    fun withWMTracingTest() {
+        val trace = withWMTracing {
+            device.pressHome()
+            device.pressRecentApps()
+        }
+
+        Truth.assertWithMessage("Could not obtain WM trace").that(trace.entries).isNotEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/MockTraceParser.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/MockTraceParser.kt
new file mode 100644
index 0000000..00c4c44
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/MockTraceParser.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.device.traces.parsers
+
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+
+class MockTraceParser(private val data: WindowManagerTrace) :
+    AbstractTraceParser<
+        WindowManagerTrace, WindowManagerState, WindowManagerState, WindowManagerTrace>() {
+    override val traceName: String = "In memory trace"
+
+    override fun createTrace(entries: List<WindowManagerState>): WindowManagerTrace =
+        WindowManagerTrace(entries.toTypedArray())
+
+    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerTrace = data
+    override fun doParseEntry(entry: WindowManagerState): WindowManagerState = entry
+    override fun getEntries(input: WindowManagerTrace): List<WindowManagerState> =
+        input.entries.toList()
+    override fun getTimestamp(entry: WindowManagerState): Timestamp =
+        CrossPlatform.timestamp.from(elapsedNanos = entry.elapsedTimestamp)
+    override fun onBeforeParse(input: WindowManagerTrace) {}
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/TraceParserTest.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/TraceParserTest.kt
new file mode 100644
index 0000000..37ef161
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/TraceParserTest.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.device.traces.parsers
+
+import android.tools.InitRule
+import android.tools.common.CrossPlatform
+import android.tools.common.Timestamp
+import android.tools.common.parsers.AbstractTraceParser
+import android.tools.utils.MockWindowManagerTraceBuilder
+import android.tools.utils.MockWindowStateBuilder
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [AbstractTraceParser] (for trace slicing) */
+class TraceParserTest {
+    @Test
+    fun canSliceWithAllBefore() {
+        testSliceUsingElapsedTimestamp(
+            CrossPlatform.timestamp.min().elapsedNanos,
+            mockTraceForSliceTests.entries.first().timestamp.elapsedNanos - 1,
+            listOf<Long>()
+        )
+    }
+
+    @Test
+    fun canSliceWithAllAfter() {
+        val from = mockTraceForSliceTests.entries.last().elapsedTimestamp + 5
+        val to = mockTraceForSliceTests.entries.last().elapsedTimestamp + 20
+        val splitLayersTraceWithoutInitialEntry =
+            MockTraceParser(mockTraceForSliceTests)
+                .parse(
+                    mockTraceForSliceTests,
+                    CrossPlatform.timestamp.from(elapsedNanos = from),
+                    CrossPlatform.timestamp.from(elapsedNanos = to),
+                    addInitialEntry = false
+                )
+        Truth.assertThat(splitLayersTraceWithoutInitialEntry.entries).isEmpty()
+
+        val splitLayersTraceWithInitialEntry =
+            MockTraceParser(mockTraceForSliceTests)
+                .parse(
+                    mockTraceForSliceTests,
+                    CrossPlatform.timestamp.from(elapsedNanos = from),
+                    CrossPlatform.timestamp.from(elapsedNanos = to),
+                    addInitialEntry = true
+                )
+        Truth.assertThat(splitLayersTraceWithInitialEntry.entries).asList().hasSize(1)
+        Truth.assertThat(splitLayersTraceWithInitialEntry.entries.first().timestamp)
+            .isEqualTo(mockTraceForSliceTests.entries.last().timestamp)
+    }
+
+    @Test
+    fun canSliceInMiddle() {
+        testSliceUsingElapsedTimestamp(15L, 25L, listOf(15L, 18L, 25L))
+    }
+
+    @Test
+    fun canSliceFromBeforeFirstEntryToMiddle() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp.elapsedNanos - 1,
+            27L,
+            listOf(5L, 8L, 15L, 18L, 25L, 27L)
+        )
+    }
+
+    @Test
+    fun canSliceFromMiddleToAfterLastEntry() {
+        testSliceUsingElapsedTimestamp(
+            18L,
+            mockTraceForSliceTests.entries.last().timestamp.elapsedNanos + 5,
+            listOf(18L, 25L, 27L, 30L)
+        )
+    }
+
+    @Test
+    fun canSliceFromBeforeToAfterLastEntry() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp.elapsedNanos - 1,
+            mockTraceForSliceTests.entries.last().timestamp.elapsedNanos + 1,
+            mockTraceForSliceTests.entries.map { it.timestamp }
+        )
+    }
+
+    @Test
+    fun canSliceFromExactStartToAfterLastEntry() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp,
+            mockTraceForSliceTests.entries.last().timestamp.elapsedNanos + 1,
+            mockTraceForSliceTests.entries.map { it.timestamp }
+        )
+    }
+
+    @Test
+    fun canSliceFromExactStartToExactEnd() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp,
+            mockTraceForSliceTests.entries.last().timestamp,
+            mockTraceForSliceTests.entries.map { it.timestamp }
+        )
+    }
+
+    @Test
+    fun canSliceFromExactStartToMiddle() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp,
+            18L,
+            listOf(5L, 8L, 15L, 18L)
+        )
+    }
+
+    @Test
+    fun canSliceFromMiddleToExactEnd() {
+        testSliceUsingElapsedTimestamp(
+            18L,
+            mockTraceForSliceTests.entries.last().timestamp,
+            listOf(18L, 25L, 27L, 30L)
+        )
+    }
+
+    @Test
+    fun canSliceFromBeforeToExactEnd() {
+        testSliceUsingElapsedTimestamp(
+            mockTraceForSliceTests.entries.first().timestamp.elapsedNanos - 1,
+            mockTraceForSliceTests.entries.last().timestamp,
+            mockTraceForSliceTests.entries.map { it.timestamp }
+        )
+    }
+
+    @Test
+    fun canSliceSameStartAndEnd() {
+        testSliceUsingElapsedTimestamp(15L, 15L, listOf(15L))
+    }
+
+    @JvmName("testSliceUsingElapsedTimestamp1")
+    private fun testSliceUsingElapsedTimestamp(from: Long, to: Long, expected: List<Timestamp>) {
+        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
+    }
+
+    @JvmName("testSliceUsingElapsedTimestamp1")
+    private fun testSliceUsingElapsedTimestamp(
+        from: Timestamp?,
+        to: Long,
+        expected: List<Timestamp>
+    ) {
+        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
+    }
+
+    private fun testSliceUsingElapsedTimestamp(
+        from: Long,
+        to: Timestamp?,
+        expected: List<Timestamp>
+    ) {
+        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
+    }
+
+    @JvmName("testSliceUsingElapsedTimestamp1")
+    private fun testSliceUsingElapsedTimestamp(from: Long, to: Timestamp?, expected: List<Long>) {
+        return testSliceUsingElapsedTimestamp(
+            from,
+            to?.elapsedNanos ?: error("missing elapsed timestamp"),
+            expected
+        )
+    }
+
+    @JvmName("testSliceUsingElapsedTimestamp2")
+    private fun testSliceUsingElapsedTimestamp(
+        from: Timestamp?,
+        to: Timestamp?,
+        expected: List<Timestamp>
+    ) {
+        return testSliceUsingElapsedTimestamp(
+            from?.elapsedNanos ?: error("missing elapsed timestamp"),
+            to?.elapsedNanos ?: error("missing elapsed timestamp"),
+            expected.map { it.elapsedNanos }
+        )
+    }
+
+    private fun testSliceUsingElapsedTimestamp(from: Timestamp?, to: Long, expected: List<Long>) {
+        return testSliceUsingElapsedTimestamp(
+            from?.elapsedNanos ?: error("missing elapsed timestamp"),
+            to,
+            expected
+        )
+    }
+
+    private fun testSliceUsingElapsedTimestamp(from: Long, to: Long, expected: List<Long>) {
+        require(from <= to) { "`from` not before `to`" }
+        val fromBefore = from < mockTraceForSliceTests.entries.first().timestamp.elapsedNanos
+        val fromAfter = from < mockTraceForSliceTests.entries.first().timestamp.elapsedNanos
+
+        val toBefore = to < mockTraceForSliceTests.entries.first().timestamp.elapsedNanos
+        val toAfter = mockTraceForSliceTests.entries.last().timestamp.elapsedNanos < to
+
+        require(
+            fromBefore ||
+                fromAfter ||
+                mockTraceForSliceTests.entries.map { it.timestamp.elapsedNanos }.contains(from)
+        ) { "`from` need to be in the trace or before or after all entries" }
+        require(
+            toBefore ||
+                toAfter ||
+                mockTraceForSliceTests.entries.map { it.timestamp.elapsedNanos }.contains(to)
+        ) { "`to` need to be in the trace or before or after all entries" }
+
+        testSliceWithOutInitialEntry(from, to, expected)
+        if (!fromAfter) {
+            testSliceWithOutInitialEntry(from - 1, to, expected)
+            testSliceWithOutInitialEntry(from - 1, to + 1, expected)
+        }
+        if (!toBefore) {
+            testSliceWithOutInitialEntry(from, to + 1, expected)
+        }
+
+        testSliceWithInitialEntry(from, to, expected)
+        if (!fromBefore) {
+            if (from < to) {
+                testSliceWithInitialEntry(from + 1, to, expected)
+            }
+            testSliceWithInitialEntry(from + 1, to + 1, expected)
+        }
+        if (!toBefore) {
+            testSliceWithInitialEntry(from, to + 1, expected)
+        }
+    }
+
+    private fun testSliceWithOutInitialEntry(from: Long, to: Long, expected: List<Long>) {
+        val splitLayersTrace =
+            MockTraceParser(mockTraceForSliceTests)
+                .parse(
+                    mockTraceForSliceTests,
+                    CrossPlatform.timestamp.from(elapsedNanos = from),
+                    CrossPlatform.timestamp.from(elapsedNanos = to),
+                    addInitialEntry = false
+                )
+        Truth.assertThat(splitLayersTrace.entries.map { it.timestamp.elapsedNanos })
+            .isEqualTo(expected)
+    }
+
+    private fun testSliceWithInitialEntry(from: Long, to: Long, expected: List<Long>) {
+        val splitLayersTraceWithStartEntry =
+            MockTraceParser(mockTraceForSliceTests)
+                .parse(
+                    mockTraceForSliceTests,
+                    CrossPlatform.timestamp.from(elapsedNanos = from),
+                    CrossPlatform.timestamp.from(elapsedNanos = to),
+                    addInitialEntry = true
+                )
+        Truth.assertThat(splitLayersTraceWithStartEntry.entries.map { it.timestamp.elapsedNanos })
+            .isEqualTo(expected)
+    }
+
+    companion object {
+        val mockTraceForSliceTests =
+            MockWindowManagerTraceBuilder(
+                    entries =
+                        mutableListOf(
+                            MockWindowStateBuilder(timestamp = 5),
+                            MockWindowStateBuilder(timestamp = 8),
+                            MockWindowStateBuilder(timestamp = 15),
+                            MockWindowStateBuilder(timestamp = 18),
+                            MockWindowStateBuilder(timestamp = 25),
+                            MockWindowStateBuilder(timestamp = 27),
+                            MockWindowStateBuilder(timestamp = 30),
+                        )
+                )
+                .build()
+
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/WindowManagerStateHelperTest.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/WindowManagerStateHelperTest.kt
new file mode 100644
index 0000000..fbc53c3
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/WindowManagerStateHelperTest.kt
@@ -0,0 +1,400 @@
+/*
+ * 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.device.traces.parsers
+
+import android.annotation.SuppressLint
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.common.CrossPlatform
+import android.tools.common.Rotation
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Matrix33
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.IComponentName
+import android.tools.common.flicker.subject.wm.WindowManagerStateSubject
+import android.tools.common.traces.DeviceStateDump
+import android.tools.common.traces.surfaceflinger.HwcCompositionType
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.LayerTraceEntryBuilder
+import android.tools.common.traces.surfaceflinger.Transform
+import android.tools.common.traces.wm.WindowManagerState
+import android.tools.common.traces.wm.WindowManagerTrace
+import android.tools.readWmTraceFromDumpFile
+import android.tools.readWmTraceFromFile
+import androidx.test.filters.FlakyTest
+import androidx.test.platform.app.InstrumentationRegistry
+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.entries.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.entries.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.entries.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.entries.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.entries.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.entries.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.entries.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.entries.first(),
+                supplier,
+                numRetries = trace.entries.size,
+                retryIntervalMs = 1
+            )
+        WindowManagerStateSubject(helper.wmState).hasRotation(Rotation.ROTATION_0)
+        helper.StateSyncBuilder().withRotation(Rotation.ROTATION_270).waitFor()
+        WindowManagerStateSubject(helper.wmState).hasRotation(Rotation.ROTATION_270)
+        helper.StateSyncBuilder().withRotation(Rotation.ROTATION_0).waitFor()
+        WindowManagerStateSubject(helper.wmState).hasRotation(Rotation.ROTATION_0)
+    }
+
+    @Test
+    fun canDetectResumedActivitiesInStacks() {
+        val trace = readWmTraceFromDumpFile("wm_trace_resumed_activities_in_stack.pb")
+        val entry = trace.entries.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.entries.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()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/surfaceflinger/LayerTraceParserTest.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/surfaceflinger/LayerTraceParserTest.kt
new file mode 100644
index 0000000..29ea706
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/surfaceflinger/LayerTraceParserTest.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.device.traces.parsers.surfaceflinger
+
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.readAsset
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+class LayerTraceParserTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun canParseFromTrace() {
+        val trace =
+            LayersTraceParser(legacyTrace = true).parse(readAsset("layers_trace_occluded.pb"))
+        Truth.assertWithMessage("Trace").that(trace.entries).asList().isNotEmpty()
+        Truth.assertWithMessage("Trace contains entry")
+            .that(trace.entries.map { it.elapsedTimestamp })
+            .contains(1700382131522L)
+    }
+
+    @Test
+    fun canParseFromDumpWithDisplay() {
+        val trace =
+            LayersTraceParser(legacyTrace = true).parse(readAsset("layers_dump_with_display.pb"))
+        Truth.assertWithMessage("Dump").that(trace.entries).asList().isNotEmpty()
+        Truth.assertWithMessage("Dump contains display")
+            .that(trace.entries.first().displays)
+            .asList()
+            .isNotEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParserTest.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParserTest.kt
new file mode 100644
index 0000000..bf484c8
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerDumpParserTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.common.io.TraceType
+import android.tools.device.traces.getCurrentState
+import android.tools.readAsset
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [WindowManagerDumpParser] */
+class WindowManagerDumpParserTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun canParseFromStoredDump() {
+        val trace = WindowManagerDumpParser().parse(readAsset("wm_trace_dump.pb"))
+        Truth.assertWithMessage("Unable to parse dump").that(trace.entries).asList().hasSize(1)
+    }
+
+    @Test
+    fun canParseFromNewDump() {
+        val data = getCurrentState(TraceType.WM_DUMP)
+        val trace = WindowManagerDumpParser().parse(data.first)
+        Truth.assertWithMessage("Unable to parse dump").that(trace.entries).asList().hasSize(1)
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParserTest.kt b/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParserTest.kt
new file mode 100644
index 0000000..48ddf79
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/device/traces/parsers/wm/WindowManagerTraceParserTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.device.traces.parsers.wm
+
+import android.tools.InitRule
+import android.tools.common.Cache
+import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
+import android.tools.readAsset
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+
+/** Tests for [WindowManagerTraceParser] */
+class WindowManagerTraceParserTest {
+    @Before
+    fun before() {
+        Cache.clear()
+    }
+
+    @Test
+    fun canParseAllEntriesFromStoredTrace() {
+        val trace =
+            WindowManagerTraceParser(legacyTrace = true)
+                .parse(readAsset("wm_trace_openchrome.pb"), clearCache = false)
+        val firstEntry = trace.entries[0]
+        Truth.assertThat(firstEntry.timestamp.elapsedNanos).isEqualTo(9213763541297L)
+        Truth.assertThat(firstEntry.windowStates.size).isEqualTo(10)
+        Truth.assertThat(firstEntry.visibleWindows.size).isEqualTo(5)
+        Truth.assertThat(trace.entries[trace.entries.size - 1].timestamp.elapsedNanos)
+            .isEqualTo(9216093628925L)
+    }
+
+    @Test
+    fun canParseAllEntriesFromNewTrace() {
+        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+        val data =
+            WindowManagerTraceMonitor().withTracing {
+                device.pressHome()
+                device.pressRecentApps()
+            }
+        val trace = WindowManagerTraceParser().parse(data, clearCache = false)
+        Truth.assertThat(trace.entries).asList().isNotEmpty()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/KotlinMockito.kt b/libraries/flicker/test/src/android/tools/utils/KotlinMockito.kt
new file mode 100644
index 0000000..2c29800
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/KotlinMockito.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.utils
+
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+
+class KotlinMockito {
+    companion object {
+        inline fun <T> any(type: Class<T>): T {
+            Mockito.any(type)
+            return uninitialized()
+        }
+
+        inline fun <reified T : Any> argThat(noinline predicate: T.() -> Boolean): T {
+            return ArgumentMatchers.argThat { arg: T? -> arg?.predicate() ?: false }
+                ?: uninitialized()
+        }
+
+        inline fun <T> uninitialized(): T = null as T
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockLayerBuilder.kt b/libraries/flicker/test/src/android/tools/utils/MockLayerBuilder.kt
new file mode 100644
index 0000000..9f2d64c
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockLayerBuilder.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.utils
+
+import android.tools.common.datatypes.ActiveBuffer
+import android.tools.common.datatypes.Color
+import android.tools.common.datatypes.Matrix33
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.RectF
+import android.tools.common.datatypes.Region
+import android.tools.common.traces.surfaceflinger.Flag
+import android.tools.common.traces.surfaceflinger.HwcCompositionType
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.Transform
+
+class MockLayerBuilder(private val name: String) {
+    companion object {
+        private var idCounter = 1
+    }
+
+    private val children = mutableListOf<MockLayerBuilder>()
+    private var type = "BufferStateLayer"
+    private val id = idCounter++
+    private var parentId = -1
+    private var isVisible = true
+    private var absoluteBounds: Rect? = null
+    private var zIndex = 0
+    private var isOpaque = true
+
+    fun addChild(layer: MockLayerBuilder): MockLayerBuilder = apply { this.children.add(layer) }
+
+    fun setContainerLayer(): MockLayerBuilder = apply {
+        this.type = "ContainerLayer"
+        this.isOpaque = false
+        this.isVisible = false
+    }
+
+    fun setVisible(): MockLayerBuilder = apply { this.isVisible = true }
+
+    fun setInvisible(): MockLayerBuilder = apply { this.isVisible = false }
+
+    fun addChildren(rootLayers: Collection<MockLayerBuilder>): MockLayerBuilder = apply {
+        rootLayers.forEach { addChild(it) }
+    }
+
+    fun setAbsoluteBounds(bounds: Rect): MockLayerBuilder = apply { this.absoluteBounds = bounds }
+
+    fun build(): Layer {
+        val absoluteBounds = this.absoluteBounds
+        requireNotNull(absoluteBounds) { "Layer has no bounds set..." }
+
+        val transform = Transform.from(0, Matrix33.identity(0f, 0f))
+
+        val thisLayer =
+            Layer.from(
+                name,
+                id,
+                parentId,
+                z = zIndex,
+                visibleRegion = if (isVisible) Region.from(absoluteBounds) else Region.EMPTY,
+                activeBuffer = ActiveBuffer.from(absoluteBounds.width, absoluteBounds.height, 1, 1),
+                flags = if (isVisible) 0 else Flag.HIDDEN.value,
+                bounds = absoluteBounds.toRectF(),
+                color = Color.DEFAULT,
+                isOpaque = isVisible && isOpaque,
+                shadowRadius = 0f,
+                cornerRadius = 0f,
+                type = type,
+                screenBounds = absoluteBounds.toRectF(),
+                transform = transform,
+                sourceBounds = absoluteBounds.toRectF(),
+                currFrame = 0,
+                effectiveScalingMode = 0,
+                bufferTransform = transform,
+                hwcCompositionType = HwcCompositionType.INVALID,
+                hwcCrop = RectF.EMPTY,
+                hwcFrame = Rect.EMPTY,
+                crop = absoluteBounds,
+                backgroundBlurRadius = 0,
+                isRelativeOf = false,
+                zOrderRelativeOfId = -1,
+                stackId = 0,
+                requestedTransform = transform,
+                requestedColor = Color.DEFAULT,
+                cornerRadiusCrop = RectF.EMPTY,
+                inputTransform = transform,
+                inputRegion = Region.from(absoluteBounds),
+                excludesCompositionState = false
+            )
+
+        val layers = mutableListOf<Layer>()
+        layers.add(thisLayer)
+
+        // var indexCount = 1
+        children.forEach { child ->
+            child.parentId = this.id
+            // it.zIndex = this.zIndex + indexCount
+
+            val childAbsoluteBounds = child.absoluteBounds
+            if (childAbsoluteBounds == null) {
+                child.absoluteBounds = this.absoluteBounds
+            } else {
+                child.absoluteBounds = childAbsoluteBounds.intersection(absoluteBounds)
+            }
+
+            thisLayer.addChild(child.build())
+        }
+
+        return thisLayer
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockLayerTraceBuilderTest.kt b/libraries/flicker/test/src/android/tools/utils/MockLayerTraceBuilderTest.kt
new file mode 100644
index 0000000..9c2f804
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockLayerTraceBuilderTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.utils
+
+import android.tools.InitRule
+import android.tools.common.datatypes.Rect
+import com.google.common.truth.Truth
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Test for the MockLayerTraceBuilder utilities. To run this test: `atest
+ * FlickerLibTest:MockLayerTraceBuilderTest`
+ */
+class MockLayerTraceBuilderTest {
+    @Test
+    fun containerLayerIsInvisible() {
+        val mockLayer =
+            MockLayerBuilder("Mock Layer")
+                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                .setContainerLayer()
+                .build()
+
+        Truth.assertThat(mockLayer.isVisible).isFalse()
+    }
+
+    @Test
+    fun childrenLayerInheritsParentBounds() {
+        val mockLayer =
+            MockLayerBuilder("Parent Mock Layer")
+                .setContainerLayer()
+                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                .addChild(MockLayerBuilder("Child Mock Layer"))
+                .build()
+
+        Truth.assertThat(mockLayer.children[0].screenBounds).isEqualTo(mockLayer.screenBounds)
+        Truth.assertThat(mockLayer.children[0].bounds).isEqualTo(mockLayer.bounds)
+    }
+
+    @Test
+    fun canAddChildLayer() {
+        val mockLayer =
+            MockLayerBuilder("Parent Mock Layer")
+                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                .addChild(MockLayerBuilder("Child Mock Layer"))
+                .build()
+
+        Truth.assertThat(mockLayer.children).isNotEmpty()
+    }
+
+    @Test
+    fun canSetLayerVisibility() {
+        val mockLayer =
+            MockLayerBuilder("Mock Layer")
+                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                .setInvisible()
+                .build()
+
+        Truth.assertThat(mockLayer.isVisible).isFalse()
+    }
+
+    @Test
+    fun invisibleLayerHasNoVisibleBounds() {
+        val mockLayer =
+            MockLayerBuilder("Mock Layer")
+                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
+                .setInvisible()
+                .build()
+
+        Truth.assertThat(mockLayer.visibleRegion?.isEmpty ?: true).isTrue()
+    }
+
+    companion object {
+        @ClassRule @JvmField val initRule = InitRule()
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockLayerTraceEntryBuilder.kt b/libraries/flicker/test/src/android/tools/utils/MockLayerTraceEntryBuilder.kt
new file mode 100644
index 0000000..e4e101e
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockLayerTraceEntryBuilder.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.utils
+
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Size
+import android.tools.common.traces.surfaceflinger.Display
+import android.tools.common.traces.surfaceflinger.Layer
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.Transform
+
+class MockLayerTraceEntryBuilder() {
+    private val displays = mutableListOf<Display>()
+    private val layers = mutableListOf<Layer>()
+    private val bounds = Rect.from(0, 0, 1080, 1920)
+    var timestamp = -1L
+        private set
+
+    constructor(timestamp: Long) : this() {
+        setTimestamp(timestamp)
+    }
+
+    init {
+        if (timestamp <= 0L) {
+            timestamp = ++lastTimestamp
+        }
+    }
+
+    fun addDisplay(rootLayers: List<MockLayerBuilder>): MockLayerTraceEntryBuilder = apply {
+        val displayLayer =
+            MockLayerBuilder("Display").setAbsoluteBounds(bounds).addChildren(rootLayers).build()
+        val displayId = 1UL
+        val stackId = 1
+        this.displays.add(
+            Display.from(
+                id = displayId,
+                name = "Display",
+                layerStackId = stackId,
+                size = Size.from(bounds.width, bounds.height),
+                layerStackSpace = bounds,
+                transform = Transform.EMPTY,
+                isVirtual = false
+            )
+        )
+        this.layers.add(displayLayer)
+    }
+
+    fun setTimestamp(timestamp: Long): MockLayerTraceEntryBuilder = apply {
+        require(timestamp > 0) { "Timestamp must be a positive value." }
+        this.timestamp = timestamp
+        lastTimestamp = timestamp
+    }
+
+    fun build(): LayerTraceEntry {
+        return LayerTraceEntry(
+            elapsedTimestamp = timestamp,
+            clockTimestamp = null,
+            hwcBlob = "",
+            where = "",
+            displays = displays.toTypedArray(),
+            vSyncId = 100,
+            _rootLayers = layers.toTypedArray()
+        )
+    }
+
+    companion object {
+        private var lastTimestamp = 1L
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockLayersTraceBuilder.kt b/libraries/flicker/test/src/android/tools/utils/MockLayersTraceBuilder.kt
new file mode 100644
index 0000000..4b215f1
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockLayersTraceBuilder.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.utils
+
+import android.tools.common.traces.surfaceflinger.LayerTraceEntry
+import android.tools.common.traces.surfaceflinger.LayersTrace
+
+class MockLayersTraceBuilder(
+    private var entries: MutableList<MockLayerTraceEntryBuilder> = mutableListOf()
+) {
+    fun addEntry(entry: MockLayerTraceEntryBuilder) {
+        entries.add(entry)
+    }
+
+    fun sortEntriesBasedOfCurrentTimestamps() {
+        entries.sortBy { it.timestamp }
+    }
+
+    fun build(): LayersTrace {
+        require(entries.zipWithNext { prev, cur -> prev.timestamp < cur.timestamp }.all { it }) {
+            "Timestamps not strictly increasing between entries."
+        }
+
+        val entries = entries.map { it.build() }.toTypedArray<LayerTraceEntry>()
+        return LayersTrace(entries)
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockWindowManagerTraceBuilder.kt b/libraries/flicker/test/src/android/tools/utils/MockWindowManagerTraceBuilder.kt
new file mode 100644
index 0000000..7574f14
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockWindowManagerTraceBuilder.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.utils
+
+import android.tools.common.traces.wm.WindowManagerTrace
+
+class MockWindowManagerTraceBuilder(
+    private var entries: MutableList<MockWindowStateBuilder> = mutableListOf()
+) {
+    fun addEntry(entry: MockWindowStateBuilder) {
+        entries.add(entry)
+    }
+
+    fun sortEntriesBasedOfCurrentTimestamps() {
+        entries.sortBy { it.timestamp }
+    }
+
+    fun build(): WindowManagerTrace {
+        require(entries.zipWithNext { prev, cur -> prev.timestamp < cur.timestamp }.all { it }) {
+            "Timestamps not strictly increasing between entries."
+        }
+
+        val entries = entries.map { it.build() }.toTypedArray()
+        return WindowManagerTrace(entries)
+    }
+}
diff --git a/libraries/flicker/test/src/android/tools/utils/MockWindowStateBuilder.kt b/libraries/flicker/test/src/android/tools/utils/MockWindowStateBuilder.kt
new file mode 100644
index 0000000..8189339
--- /dev/null
+++ b/libraries/flicker/test/src/android/tools/utils/MockWindowStateBuilder.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.utils
+
+import android.tools.common.traces.wm.ConfigurationContainer
+import android.tools.common.traces.wm.KeyguardControllerState
+import android.tools.common.traces.wm.RootWindowContainer
+import android.tools.common.traces.wm.WindowContainer
+import android.tools.common.traces.wm.WindowManagerState
+
+class MockWindowStateBuilder() {
+    var timestamp = -1L
+        private set
+
+    constructor(timestamp: Long) : this() {
+        setTimestamp(timestamp)
+    }
+
+    init {
+        if (timestamp <= 0L) {
+            timestamp = ++lastTimestamp
+        }
+    }
+
+    fun setTimestamp(timestamp: Long): MockWindowStateBuilder = apply {
+        require(timestamp > 0) { "Timestamp must be a positive value." }
+        this.timestamp = timestamp
+        lastTimestamp = timestamp
+    }
+
+    fun build(): WindowManagerState {
+        return WindowManagerState(
+            elapsedTimestamp = timestamp,
+            clockTimestamp = null,
+            where = "where",
+            policy = null,
+            focusedApp = "focusedApp",
+            focusedDisplayId = 1,
+            _focusedWindow = "focusedWindow",
+            inputMethodWindowAppToken = "",
+            isHomeRecentsComponent = false,
+            isDisplayFrozen = false,
+            _pendingActivities = emptyArray(),
+            root =
+                RootWindowContainer(
+                    WindowContainer(
+                        title = "root container",
+                        token = "",
+                        orientation = 1,
+                        layerId = 1,
+                        _isVisible = true,
+                        configurationContainer = ConfigurationContainer(null, null, null),
+                        children = emptyArray(),
+                        computedZ = 0
+                    )
+                ),
+            keyguardControllerState =
+                KeyguardControllerState.from(
+                    isAodShowing = false,
+                    isKeyguardShowing = false,
+                    keyguardOccludedStates = emptyMap()
+                )
+        )
+    }
+
+    companion object {
+        private var lastTimestamp = 1L
+    }
+}
diff --git a/libraries/flicker/test/src/com/android/server/wm/InitRule.kt b/libraries/flicker/test/src/com/android/server/wm/InitRule.kt
deleted file mode 100644
index ef594da..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/InitRule.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 com.android.server.wm
-
-import android.annotation.SuppressLint
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.TimestampFactory
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.parser.ANDROID_LOGGER
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/** Standard initialization rule for all flicker tests */
-@SuppressLint("VisibleForTests")
-class InitRule : TestRule {
-    override fun apply(base: Statement, description: Description?): Statement {
-        CrossPlatform.setLogger(ANDROID_LOGGER)
-            .setTimestampFactory(TimestampFactory { Utils.formatRealTimestamp(it) })
-        DataStore.clear()
-        Cache.clear()
-        RunStatus.ALL.forEach { outputFileName(it).deleteIfExists() }
-        return base
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt
deleted file mode 100644
index f24dec1..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/EventLogSubjectTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.io.ParsedTracesReader
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Contains [EventLogSubject] tests. To run this test: `atest FlickerLibTest:EventLogSubjectTest`
- */
-class EventLogSubjectTest {
-    @Test
-    fun canDetectFocusChanges() {
-        val reader =
-            ParsedTracesReader(
-                eventLog =
-                    EventLog(
-                        arrayOf(
-                            FocusEvent(
-                                CrossPlatform.timestamp.from(unixNanos = 0),
-                                "WinB",
-                                FocusEvent.Type.GAINED,
-                                "test",
-                                0,
-                                "0",
-                                0
-                            ),
-                            FocusEvent(
-                                CrossPlatform.timestamp.from(unixNanos = 0),
-                                "test WinA window",
-                                FocusEvent.Type.LOST,
-                                "test",
-                                0,
-                                "0",
-                                0
-                            ),
-                            FocusEvent(
-                                CrossPlatform.timestamp.from(unixNanos = 0),
-                                "WinB",
-                                FocusEvent.Type.LOST,
-                                "test",
-                                0,
-                                "0",
-                                0
-                            ),
-                            FocusEvent(
-                                CrossPlatform.timestamp.from(unixNanos = 0),
-                                "test WinC",
-                                FocusEvent.Type.GAINED,
-                                "test",
-                                0,
-                                "0",
-                                0
-                            )
-                        )
-                    )
-            )
-        val subjectsParser = SubjectsParser(reader)
-
-        val subject = subjectsParser.eventLogSubject ?: error("Event log subject not built")
-        subject.focusChanges("WinA", "WinB", "WinC")
-        subject.focusChanges("WinA", "WinB")
-        subject.focusChanges("WinB", "WinC")
-        subject.focusChanges("WinA")
-        subject.focusChanges("WinB")
-        subject.focusChanges("WinC")
-    }
-
-    @Test
-    fun canDetectFocusDoesNotChange() {
-        val reader = ParsedTracesReader(eventLog = EventLog(emptyArray()))
-        val subjectsParser = SubjectsParser(reader)
-
-        val subject = subjectsParser.eventLogSubject ?: error("Event log subject not built")
-        subject.focusDoesNotChange()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestFactoryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestFactoryTest.kt
deleted file mode 100644
index 41698f6..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestFactoryTest.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.flicker
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [FlickerTestFactory] tests.
- *
- * To run this test: `atest FlickerLibTest:FlickerTestFactoryRunnerTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FlickerTestFactoryTest {
-    @Test
-    fun checkBuildTest() {
-        val actual = FlickerTestFactory.nonRotationTests()
-        Truth.assertWithMessage("Flicker should create tests for 0 and 90 degrees")
-            .that(actual)
-            .hasSize(4)
-    }
-
-    @Test
-    fun checkBuildRotationTest() {
-        val actual = FlickerTestFactory.rotationTests()
-        Truth.assertWithMessage("Flicker should create tests for 0 and 90 degrees")
-            .that(actual)
-            .hasSize(4)
-    }
-
-    @Test
-    fun checkBuildCustomRotationsTest() {
-        val rotations =
-            listOf(
-                PlatformConsts.Rotation.ROTATION_0,
-                PlatformConsts.Rotation.ROTATION_90,
-                PlatformConsts.Rotation.ROTATION_180,
-                PlatformConsts.Rotation.ROTATION_270
-            )
-        val actual = FlickerTestFactory.rotationTests(supportedRotations = rotations)
-        // Should have config for each rotation pair
-        Truth.assertWithMessage("Flicker should create tests for 0/90/180/270 degrees")
-            .that(actual)
-            .hasSize(24)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestTest.kt
deleted file mode 100644
index cbfdeb5..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/FlickerTestTest.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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.flicker
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [FlickerTest] */
-@SuppressLint("VisibleForTests")
-class FlickerTestTest {
-    private var executionCount = 0
-
-    @Before
-    fun setup() {
-        executionCount = 0
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        DataStore.clear()
-    }
-
-    @Test
-    fun failsWithoutScenario() {
-        val actual = FlickerTest()
-        val failure =
-            assertThrows<IllegalArgumentException> { actual.assertLayers { executionCount++ } }
-        assertExceptionMessage(failure, "Scenario shouldn't be empty")
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(0)
-    }
-
-    @Test
-    fun executesLayers() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayers { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.SF,
-            predicate,
-            TestTraces.LayerTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun executesLayerStart() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayersStart { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.SF,
-            predicate,
-            TestTraces.LayerTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun executesLayerEnd() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayersEnd { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.SF,
-            predicate,
-            TestTraces.LayerTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun doesNotExecuteLayersWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayers { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteLayersStartWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayersStart { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteLayersEndWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayersEnd { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteLayerTagWithoutTag() {
-        val predicate: (FlickerTest) -> Unit = { it.assertLayersTag("tag") { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.SF, predicate)
-    }
-
-    @Test
-    fun executesWm() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWm { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.WM,
-            predicate,
-            TestTraces.WMTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun executesWmStart() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWmStart { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.WM,
-            predicate,
-            TestTraces.WMTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun executesWmEnd() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWmEnd { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.WM,
-            predicate,
-            TestTraces.WMTrace.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun doesNotExecuteWmWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWm { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteWmStartWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWmStart { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteWmEndWithoutTrace() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWmEnd { executionCount++ } }
-        doExecuteAssertionWithoutTraceAndVerifyNotExecuted(TraceType.WM, predicate)
-    }
-
-    @Test
-    fun doesNotExecuteWmTagWithoutTag() {
-        val predicate: (FlickerTest) -> Unit = { it.assertWmTag("tag") { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.WM,
-            predicate,
-            TestTraces.WMTrace.FILE,
-            expectedExecutionCount = 0
-        )
-    }
-
-    @Test
-    fun executesEventLog() {
-        val predicate: (FlickerTest) -> Unit = { it.assertEventLog { executionCount++ } }
-        doWriteTraceExecuteAssertionAndVerify(
-            TraceType.EVENT_LOG,
-            predicate,
-            TestTraces.EventLog.FILE,
-            expectedExecutionCount = 2
-        )
-    }
-
-    @Test
-    fun doesNotExecuteEventLogWithoutEventLog() {
-        val predicate: (FlickerTest) -> Unit = { it.assertEventLog { executionCount++ } }
-        newTestCachedResultWriter().write()
-        val flickerWrapper = FlickerTest()
-        flickerWrapper.initialize(TEST_SCENARIO.testClass)
-        // Each assertion is executed independently and not cached, only Flicker as a Service
-        // assertions are cached
-        predicate.invoke(flickerWrapper)
-        predicate.invoke(flickerWrapper)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(0)
-    }
-
-    private fun doExecuteAssertionWithoutTraceAndVerifyNotExecuted(
-        traceType: TraceType,
-        predicate: (FlickerTest) -> Unit
-    ) =
-        doWriteTraceExecuteAssertionAndVerify(
-            traceType,
-            predicate,
-            file = null,
-            expectedExecutionCount = 0
-        )
-
-    private fun doWriteTraceExecuteAssertionAndVerify(
-        traceType: TraceType,
-        predicate: (FlickerTest) -> Unit,
-        file: File?,
-        expectedExecutionCount: Int
-    ) {
-        val writer = newTestCachedResultWriter()
-        if (file != null) {
-            writer.addTraceResult(traceType, file)
-        }
-        writer.write()
-        val flickerWrapper =
-            FlickerTest(
-                resultReaderProvider = {
-                    CachedResultReader(
-                        it,
-                        DEFAULT_TRACE_CONFIG,
-                        reader = ResultReader(DataStore.getResult(it), DEFAULT_TRACE_CONFIG)
-                    )
-                }
-            )
-        flickerWrapper.initialize(TEST_SCENARIO.testClass)
-        // Each assertion is executed independently and not cached, only Flicker as a Service
-        // assertions are cached
-        predicate.invoke(flickerWrapper)
-        predicate.invoke(flickerWrapper)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(expectedExecutionCount)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/ITraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/ITraceTest.kt
deleted file mode 100644
index 4b9f3a6..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/ITraceTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-import com.google.common.truth.Truth
-import org.junit.Test
-
-/** To run this test: `atest FlickerLibTest:ITraceTest` */
-class ITraceTest {
-    @Test
-    fun getEntryExactlyAtTest() {
-        val entry1 = SimpleTraceEntry(CrossPlatform.timestamp.from(1, 1, 1))
-        val entry2 = SimpleTraceEntry(CrossPlatform.timestamp.from(5, 5, 5))
-        val entry3 = SimpleTraceEntry(CrossPlatform.timestamp.from(25, 25, 25))
-        val trace = SimpleTrace(arrayOf(entry1, entry2, entry3))
-
-        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(1, 1, 1)))
-            .isEqualTo(entry1)
-        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(5, 5, 5)))
-            .isEqualTo(entry2)
-        Truth.assertThat(trace.getEntryExactlyAt(CrossPlatform.timestamp.from(25, 25, 25)))
-            .isEqualTo(entry3)
-
-        Truth.assertThat(
-                assertThrows<Throwable> {
-                    trace.getEntryExactlyAt(CrossPlatform.timestamp.from(6, 6, 6))
-                }
-            )
-            .hasMessageThat()
-            .contains("does not exist")
-    }
-
-    @Test
-    fun getEntryAtTest() {
-        val entry1 = SimpleTraceEntry(CrossPlatform.timestamp.from(2, 2, 2))
-        val entry2 = SimpleTraceEntry(CrossPlatform.timestamp.from(5, 5, 5))
-        val entry3 = SimpleTraceEntry(CrossPlatform.timestamp.from(25, 25, 25))
-        val trace = SimpleTrace(arrayOf(entry1, entry2, entry3))
-
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(2, 2, 2))).isEqualTo(entry1)
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(5, 5, 5))).isEqualTo(entry2)
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(25, 25, 25)))
-            .isEqualTo(entry3)
-
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(4, 4, 4))).isEqualTo(entry1)
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(6, 6, 6))).isEqualTo(entry2)
-        Truth.assertThat(trace.getEntryAt(CrossPlatform.timestamp.from(100, 100, 100)))
-            .isEqualTo(entry3)
-
-        Truth.assertThat(
-                assertThrows<Throwable> { trace.getEntryAt(CrossPlatform.timestamp.from(1, 1, 1)) }
-            )
-            .hasMessageThat()
-            .contains("No entry at or before timestamp")
-    }
-
-    class SimpleTraceEntry(override val timestamp: Timestamp) : ITraceEntry
-
-    class SimpleTrace(override val entries: Array<ITraceEntry>) : ITrace<ITraceEntry> {
-        override fun slice(
-            startTimestamp: Timestamp,
-            endTimestamp: Timestamp
-        ): ITrace<ITraceEntry> {
-            TODO("Not yet implemented")
-        }
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/TestComponents.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/TestComponents.kt
deleted file mode 100644
index 7eef89d..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/TestComponents.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-@file:JvmName("CommonConstants")
-
-package com.android.server.wm.flicker
-
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-
-internal object TestComponents {
-    @JvmStatic
-    val CHROME = ComponentNameMatcher("com.android.chrome", "com.google.android.apps.chrome.Main")
-
-    @JvmStatic
-    val CHROME_FIRST_RUN =
-        ComponentNameMatcher(
-            "com.android.chrome",
-            "org.chromium.chrome.browser.firstrun.FirstRunActivity"
-        )
-
-    @JvmStatic
-    val CHROME_SPLASH_SCREEN = ComponentNameMatcher("", "Splash Screen com.android.chrome")
-
-    @JvmStatic val DOCKER_STACK_DIVIDER = ComponentNameMatcher("", "DockedStackDivider")
-
-    @JvmStatic val IMAGINARY = ComponentNameMatcher("", "ImaginaryWindow")
-
-    @JvmStatic
-    val IME_ACTIVITY =
-        ComponentNameMatcher(
-            "com.android.server.wm.flicker.testapp",
-            "com.android.server.wm.flicker.testapp.ImeActivity"
-        )
-
-    @JvmStatic
-    val LAUNCHER =
-        ComponentNameMatcher(
-            "com.google.android.apps.nexuslauncher",
-            "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
-        )
-
-    @JvmStatic val PIP_OVERLAY = ComponentNameMatcher("", "pip-dismiss-overlay")
-
-    @JvmStatic
-    val SIMPLE_APP =
-        ComponentNameMatcher(
-            "com.android.server.wm.flicker.testapp",
-            "com.android.server.wm.flicker.testapp.SimpleActivity"
-        )
-
-    @JvmStatic
-    val NON_RESIZEABLE_APP =
-        ComponentNameMatcher(
-            "com.android.server.wm.flicker.testapp",
-            "com.android.server.wm.flicker.testapp.NonResizeableActivity"
-        )
-
-    private const val SHELL_PKG_NAME = "com.android.wm.shell.flicker.testapp"
-
-    @JvmStatic
-    val SHELL_SPLIT_SCREEN_PRIMARY =
-        ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.SplitScreenActivity")
-
-    @JvmStatic
-    val SHELL_SPLIT_SCREEN_SECONDARY =
-        ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.SplitScreenSecondaryActivity")
-
-    @JvmStatic val FIXED_APP = ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.FixedActivity")
-
-    @JvmStatic val PIP_APP = ComponentNameMatcher(SHELL_PKG_NAME, "$SHELL_PKG_NAME.PipActivity")
-
-    @JvmStatic val SCREEN_DECOR_OVERLAY = ComponentNameMatcher("", "ScreenDecorOverlay")
-
-    @JvmStatic
-    val WALLPAPER =
-        ComponentNameMatcher(
-            "",
-            "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
-        )
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/TestTraces.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/TestTraces.kt
deleted file mode 100644
index 7b208c1..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/TestTraces.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.flicker
-
-import com.android.server.wm.traces.common.CrossPlatform
-
-object TestTraces {
-    object LayerTrace {
-        private const val ASSET = "layers_trace.winscope"
-        val START_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1618663562444)
-        val SLICE_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1618715108595)
-        val END_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1620770824112)
-        val FILE
-            get() = readAssetAsFile(ASSET)
-    }
-
-    object WMTrace {
-        private const val ASSET = "wm_trace.winscope"
-        val START_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1618650751245)
-        val SLICE_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1618730362295)
-        val END_TIME = CrossPlatform.timestamp.from(elapsedNanos = 1620756218174)
-        val FILE
-            get() = readAssetAsFile(ASSET)
-    }
-
-    object EventLog {
-        private const val ASSET = "eventlog.winscope"
-        val START_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594369069951546)
-        val SLICE_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594384516466159)
-        val END_TIME = CrossPlatform.timestamp.from(unixNanos = 1670594389958451901)
-        val FILE
-            get() = readAssetAsFile(ASSET)
-    }
-
-    object TransactionTrace {
-        private const val ASSET = "transactions_trace.winscope"
-        val START_TIME =
-            CrossPlatform.timestamp.from(
-                systemUptimeNanos = 1556111744859,
-                elapsedNanos = 1556111744859
-            )
-        val VALID_SLICE_TIME =
-            CrossPlatform.timestamp.from(
-                systemUptimeNanos = 1556147625539,
-                elapsedNanos = 1556147625539
-            )
-        val INVALID_SLICE_TIME = CrossPlatform.timestamp.from(systemUptimeNanos = 1622127714039 + 1)
-        val END_TIME =
-            CrossPlatform.timestamp.from(
-                systemUptimeNanos = 1622127714039,
-                elapsedNanos = 1622127714039
-            )
-        val FILE
-            get() = readAssetAsFile(ASSET)
-    }
-
-    object TransitionTrace {
-        private const val ASSET = "transition_trace.winscope"
-        val START_TIME =
-            CrossPlatform.timestamp.from(
-                elapsedNanos = 169632392038504,
-                systemUptimeNanos = 0,
-                unixNanos = 0
-            )
-        val VALID_SLICE_TIME =
-            CrossPlatform.timestamp.from(
-                elapsedNanos = 169632392038504,
-                systemUptimeNanos = 0,
-                unixNanos = 0
-            )
-        val INVALID_SLICE_TIME =
-            CrossPlatform.timestamp.from(
-                elapsedNanos = 0L,
-                systemUptimeNanos = TransactionTrace.INVALID_SLICE_TIME.systemUptimeNanos
-            )
-        val END_TIME =
-            CrossPlatform.timestamp.from(
-                elapsedNanos = 169632392038504,
-                systemUptimeNanos = 0,
-                unixNanos = 0
-            )
-        val FILE
-            get() = readAssetAsFile(ASSET)
-    }
-
-    val TIME_5 = CrossPlatform.timestamp.from(5, 5, 5)
-    val TIME_10 = CrossPlatform.timestamp.from(10, 10, 10)
-
-    val TEST_TRACE_CONFIG =
-        TraceConfigs(
-            wmTrace =
-                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
-            layersTrace =
-                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
-            transitionsTrace =
-                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false),
-            transactionsTrace =
-                TraceConfig(required = false, allowNoChange = false, usingExistingTraces = false)
-        )
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt
deleted file mode 100644
index 7960521..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/Utils.kt
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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.flicker
-
-import android.app.Instrumentation
-import android.content.Context
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.flicker.datastore.CachedResultWriter
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.monitor.EventLogMonitor
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor
-import com.android.server.wm.flicker.monitor.ScreenRecorder
-import com.android.server.wm.flicker.monitor.TransactionsTraceMonitor
-import com.android.server.wm.flicker.monitor.TransitionsTraceMonitor
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.IScenario
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.WINSCOPE_EXT
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.parser.events.EventLogParser
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.parser.layers.LayersTraceParser
-import com.android.server.wm.traces.parser.transaction.TransactionsTraceParser
-import com.android.server.wm.traces.parser.transition.TransitionsTraceParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerDumpParser
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
-import com.google.common.io.ByteStreams
-import com.google.common.truth.StringSubject
-import com.google.common.truth.Truth
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
-import java.util.zip.ZipInputStream
-import org.mockito.Mockito
-
-internal val TEST_SCENARIO = ScenarioBuilder().forClass("test").build()
-
-internal fun outputFileName(status: RunStatus) =
-    File("/sdcard/flicker/${status.prefix}_test_ROTATION_0_GESTURAL_NAV.zip")
-
-internal fun newTestResultWriter() =
-    ResultWriter()
-        .forScenario(TEST_SCENARIO)
-        .withOutputDir(getDefaultFlickerOutputDir())
-        .setRunComplete()
-
-internal fun newTestCachedResultWriter() =
-    CachedResultWriter()
-        .forScenario(TEST_SCENARIO)
-        .withOutputDir(getDefaultFlickerOutputDir())
-        .setRunComplete()
-
-internal fun readWmTraceFromFile(
-    relativePath: String,
-    from: Long = Long.MIN_VALUE,
-    to: Long = Long.MAX_VALUE,
-    addInitialEntry: Boolean = true,
-    legacyTrace: Boolean = false,
-): WindowManagerTrace {
-    return try {
-        WindowManagerTraceParser(legacyTrace)
-            .parse(
-                readAsset(relativePath),
-                CrossPlatform.timestamp.from(elapsedNanos = from),
-                CrossPlatform.timestamp.from(elapsedNanos = to),
-                addInitialEntry,
-                clearCache = false
-            )
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-internal fun readWmTraceFromDumpFile(relativePath: String): WindowManagerTrace {
-    return try {
-        WindowManagerDumpParser().parse(readAsset(relativePath), clearCache = false)
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-internal fun readLayerTraceFromFile(
-    relativePath: String,
-    ignoreOrphanLayers: Boolean = true,
-    legacyTrace: Boolean = false,
-): LayersTrace {
-    return try {
-        LayersTraceParser(
-                ignoreLayersStackMatchNoDisplay = false,
-                ignoreLayersInVirtualDisplay = false,
-                legacyTrace = legacyTrace,
-            ) {
-                ignoreOrphanLayers
-            }
-            .parse(readAsset(relativePath))
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-internal fun readTransactionsTraceFromFile(relativePath: String): TransactionsTrace {
-    return try {
-        TransactionsTraceParser().parse(readAsset(relativePath))
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-internal fun readTransitionsTraceFromFile(
-    relativePath: String,
-    transactionsTrace: TransactionsTrace
-): TransitionsTrace {
-    return try {
-        TransitionsTraceParser().parse(readAsset(relativePath))
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-internal fun readEventLogFromFile(relativePath: String): EventLog {
-    return try {
-        EventLogParser().parse(readAsset(relativePath))
-    } catch (e: Exception) {
-        throw RuntimeException(e)
-    }
-}
-
-@Throws(Exception::class)
-internal fun readAsset(relativePath: String): ByteArray {
-    val context: Context = InstrumentationRegistry.getInstrumentation().context
-    val inputStream = context.resources.assets.open("testdata/$relativePath")
-    return ByteStreams.toByteArray(inputStream)
-}
-
-@Throws(IOException::class)
-fun readAssetAsFile(relativePath: String): File {
-    val context: Context = InstrumentationRegistry.getInstrumentation().context
-    return File(context.cacheDir, relativePath).also {
-        if (!it.exists()) {
-            it.outputStream().use { cache ->
-                context.assets.open("testdata/$relativePath").use { inputStream ->
-                    inputStream.copyTo(cache)
-                }
-            }
-        }
-    }
-}
-
-/**
- * Runs `r` and asserts that an exception with type `expectedThrowable` is thrown.
- * @param r the [Runnable] which is run and expected to throw.
- * @throws AssertionError if `r` does not throw, or throws a runnable that is not an instance of
- * `expectedThrowable`.
- */
-inline fun <reified ExceptionType> assertThrows(r: () -> Unit): ExceptionType {
-    try {
-        r()
-    } catch (t: Throwable) {
-        when {
-            ExceptionType::class.java.isInstance(t) -> return t as ExceptionType
-            t is Exception ->
-                throw AssertionError(
-                    "Expected ${ExceptionType::class.java}, but got '${t.javaClass}'",
-                    t
-                )
-            // Re-throw Errors and other non-Exception throwables.
-            else -> throw t
-        }
-    }
-    error("Expected exception ${ExceptionType::class.java}, but nothing was thrown")
-}
-
-fun assertFailureFact(
-    failure: FlickerSubjectException,
-    factKey: String,
-    factIndex: Int = 0
-): StringSubject {
-    val matchingFacts = failure.facts.filter { it.key == factKey }
-
-    if (factIndex >= matchingFacts.size) {
-        val message = buildString {
-            appendLine("Cannot find failure fact with key '$factKey' and index $factIndex")
-            appendLine()
-            appendLine("Available facts:")
-            failure.facts.forEach { appendLine(it.toString()) }
-        }
-        throw AssertionError(message)
-    }
-
-    return Truth.assertThat(matchingFacts[factIndex].value)
-}
-
-fun assertThatErrorContainsDebugInfo(error: Throwable, withBlameEntry: Boolean = true) {
-    Truth.assertThat(error).hasMessageThat().contains("What?")
-    Truth.assertThat(error).hasMessageThat().contains("Where?")
-    Truth.assertThat(error).hasMessageThat().contains("Facts")
-    Truth.assertThat(error).hasMessageThat().contains("Trace start")
-    Truth.assertThat(error).hasMessageThat().contains("Trace end")
-
-    if (withBlameEntry) {
-        Truth.assertThat(error).hasMessageThat().contains("State")
-    }
-}
-
-fun assertArchiveContainsFiles(archivePath: File, expectedFiles: List<String>) {
-    Truth.assertWithMessage("Expected trace archive `$archivePath` to exist")
-        .that(archivePath.exists())
-        .isTrue()
-
-    val archiveStream = ZipInputStream(FileInputStream(archivePath))
-
-    val actualFiles = generateSequence { archiveStream.nextEntry }.map { it.name }.toList()
-
-    Truth.assertWithMessage("Trace archive doesn't contain all expected traces")
-        .that(actualFiles)
-        .containsExactlyElementsIn(expectedFiles)
-}
-
-fun getScenarioTraces(scenario: String): FlickerBuilder.TraceFiles {
-    val randomString = (1..10).map { (('A'..'Z') + ('a'..'z')).random() }.joinToString("")
-
-    lateinit var wmTrace: File
-    lateinit var layersTrace: File
-    lateinit var transactionsTrace: File
-    lateinit var transitionsTrace: File
-    lateinit var eventLog: File
-    val traces =
-        mapOf<String, (File) -> Unit>(
-            "wm_trace" to { wmTrace = it },
-            "layers_trace" to { layersTrace = it },
-            "transactions_trace" to { transactionsTrace = it },
-            "transition_trace" to { transitionsTrace = it },
-            "eventlog" to { eventLog = it }
-        )
-    for ((traceName, resultSetter) in traces.entries) {
-        val traceBytes = readAsset("scenarios/$scenario/$traceName$WINSCOPE_EXT")
-        val traceFile =
-            getDefaultFlickerOutputDir().resolve("${traceName}_$randomString$WINSCOPE_EXT")
-        traceFile.parentFile.mkdirs()
-        traceFile.createNewFile()
-        traceFile.writeBytes(traceBytes)
-        resultSetter.invoke(traceFile)
-    }
-
-    return FlickerBuilder.TraceFiles(
-        wmTrace,
-        layersTrace,
-        transactionsTrace,
-        transitionsTrace,
-        eventLog
-    )
-}
-
-fun assertExceptionMessage(error: Throwable?, expectedValue: String) {
-    Truth.assertWithMessage("Expected exception")
-        .that(error)
-        .hasMessageThat()
-        .contains(expectedValue)
-}
-
-fun assertExceptionMessageCause(error: Throwable?, expectedValue: String) {
-    Truth.assertWithMessage("Expected cause")
-        .that(error)
-        .hasCauseThat()
-        .hasMessageThat()
-        .contains(expectedValue)
-}
-
-fun createMockedFlicker(
-    setup: List<IFlickerTestData.() -> Unit> = emptyList(),
-    teardown: List<IFlickerTestData.() -> Unit> = emptyList(),
-    transitions: List<IFlickerTestData.() -> Unit> = emptyList(),
-    extraMonitor: ITransitionMonitor? = null
-): IFlickerTestData {
-    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
-    val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
-    val monitors: MutableList<ITransitionMonitor> =
-        mutableListOf(WindowManagerTraceMonitor(), LayersTraceMonitor())
-    extraMonitor?.let { monitors.add(it) }
-    Mockito.`when`(mockedFlicker.wmHelper).thenReturn(WindowManagerStateHelper())
-    Mockito.`when`(mockedFlicker.device).thenReturn(uiDevice)
-    Mockito.`when`(mockedFlicker.outputDir).thenReturn(getDefaultFlickerOutputDir())
-    Mockito.`when`(mockedFlicker.traceMonitors).thenReturn(monitors)
-    Mockito.`when`(mockedFlicker.transitionSetup).thenReturn(setup)
-    Mockito.`when`(mockedFlicker.transitionTeardown).thenReturn(teardown)
-    Mockito.`when`(mockedFlicker.transitions).thenReturn(transitions)
-    return mockedFlicker
-}
-
-fun captureTrace(scenario: IScenario, actions: () -> Unit): ResultReader {
-    if (scenario.isEmpty) {
-        ScenarioBuilder().forClass("UNNAMED_CAPTURE").build()
-    }
-    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    val writer =
-        ResultWriter()
-            .forScenario(scenario)
-            .withOutputDir(getDefaultFlickerOutputDir())
-            .setRunComplete()
-    val monitors =
-        listOf(
-            ScreenRecorder(instrumentation.targetContext),
-            EventLogMonitor(),
-            TransactionsTraceMonitor(),
-            TransitionsTraceMonitor(),
-            WindowManagerTraceMonitor(),
-            LayersTraceMonitor()
-        )
-    try {
-        monitors.forEach { it.start() }
-        actions.invoke()
-    } finally {
-        monitors.forEach { it.stop(writer) }
-    }
-    val result = writer.write()
-
-    return ResultReader(result, DEFAULT_TRACE_CONFIG)
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunnerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunnerTest.kt
deleted file mode 100644
index 6ed8a96..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/ArtifactAssertionRunnerTest.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.flicker.monitor.EventLogMonitor
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Tests for [ArtifactAssertionRunner]
- *
- * run with `atest FlickerLibTest:ArtifactAssertionRunnerTest`
- */
-class ArtifactAssertionRunnerTest {
-    private var executionCount = 0
-
-    private val assertionSuccess = newAssertionData { executionCount++ }
-    private val assertionFailure = newAssertionData {
-        executionCount++
-        error(Consts.FAILURE)
-    }
-
-    @Before
-    fun setup() {
-        executionCount = 0
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        outputFileName(RunStatus.ASSERTION_FAILED).deleteIfExists()
-        outputFileName(RunStatus.ASSERTION_SUCCESS).deleteIfExists()
-    }
-
-    @Test
-    fun executes() {
-        val result = newResultReaderWithEmptySubject()
-        val runner = ArtifactAssertionRunner(result)
-        val firstAssertionResult = runner.runAssertion(assertionSuccess)
-        val lastAssertionResult = runner.runAssertion(assertionSuccess)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
-        verifyExceptionMessage(firstAssertionResult, expectSuccess = true)
-        verifyExceptionMessage(lastAssertionResult, expectSuccess = true)
-    }
-
-    @Test
-    fun executesFailure() {
-        val result = newResultReaderWithEmptySubject()
-        val runner = ArtifactAssertionRunner(result)
-        val firstAssertionResult = runner.runAssertion(assertionFailure)
-        val lastAssertionResult = runner.runAssertion(assertionFailure)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-
-        verifyExceptionMessage(firstAssertionResult, expectSuccess = false)
-        verifyExceptionMessage(lastAssertionResult, expectSuccess = false)
-        Truth.assertWithMessage("Same exception")
-            .that(firstAssertionResult)
-            .hasMessageThat()
-            .isEqualTo(lastAssertionResult?.message)
-    }
-
-    @Test
-    fun updatesRunStatusFailureFirst() {
-        val result = newResultReaderWithEmptySubject()
-        val runner = ArtifactAssertionRunner(result)
-        val firstAssertionResult = runner.runAssertion(assertionFailure)
-        val lastAssertionResult = runner.runAssertion(assertionSuccess)
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        verifyExceptionMessage(firstAssertionResult, expectSuccess = false)
-        verifyExceptionMessage(lastAssertionResult, expectSuccess = true)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-    }
-
-    @Test
-    fun updatesRunStatusFailureLast() {
-        val result = newResultReaderWithEmptySubject()
-        val runner = ArtifactAssertionRunner(result)
-        val firstAssertionResult = runner.runAssertion(assertionSuccess)
-        val lastAssertionResult = runner.runAssertion(assertionFailure)
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        verifyExceptionMessage(firstAssertionResult, expectSuccess = true)
-        verifyExceptionMessage(lastAssertionResult, expectSuccess = false)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-    }
-
-    private fun verifyExceptionMessage(actual: Throwable?, expectSuccess: Boolean) {
-        if (expectSuccess) {
-            Truth.assertWithMessage("Expected exception").that(actual).isNull()
-        } else {
-            assertExceptionMessage(actual, Consts.FAILURE)
-        }
-    }
-
-    companion object {
-        private fun newAssertionData(assertion: (FlickerSubject) -> Unit) =
-            AssertionData(AssertionTag.ALL, EventLogSubject::class, assertion)
-
-        private fun newResultReaderWithEmptySubject(): IResultData {
-            val writer = newTestResultWriter()
-            val monitor = EventLogMonitor()
-            monitor.start()
-            monitor.stop(writer)
-            return writer.write()
-        }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionDataFactoryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionDataFactoryTest.kt
deleted file mode 100644
index 5f7b6d6..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionDataFactoryTest.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-import org.junit.Test
-
-/** Tests for [AssertionDataFactoryTest] */
-class AssertionDataFactoryTest : AssertionStateDataFactoryTest() {
-    override val wmAssertionFactory: AssertionStateDataFactory
-        get() =
-            AssertionDataFactory(WindowManagerStateSubject::class, WindowManagerTraceSubject::class)
-    override val layersAssertionFactory: AssertionStateDataFactory
-        get() = AssertionDataFactory(LayerTraceEntrySubject::class, LayersTraceSubject::class)
-
-    @Test
-    fun checkBuildsTraceAssertion() {
-        validate(
-            (wmAssertionFactory as AssertionDataFactory).createTraceAssertion {},
-            WindowManagerTraceSubject::class,
-            AssertionTag.ALL
-        )
-        validate(
-            (layersAssertionFactory as AssertionDataFactory).createTraceAssertion {},
-            LayersTraceSubject::class,
-            AssertionTag.ALL
-        )
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactoryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactoryTest.kt
deleted file mode 100644
index 0d4e82d..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionStateDataFactoryTest.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.google.common.truth.Truth
-import kotlin.reflect.KClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [AssertionStateDataFactory] */
-open class AssertionStateDataFactoryTest {
-    protected open val wmAssertionFactory
-        get() = AssertionStateDataFactory(WindowManagerStateSubject::class)
-    protected open val layersAssertionFactory
-        get() = AssertionStateDataFactory(LayerTraceEntrySubject::class)
-    protected open val eventLogAssertionFactory
-        get() = AssertionStateDataFactory(EventLogSubject::class)
-
-    @Test
-    open fun checkBuildsStartAssertion() {
-        validate(
-            wmAssertionFactory.createStartStateAssertion {},
-            WindowManagerStateSubject::class,
-            AssertionTag.START
-        )
-        validate(
-            layersAssertionFactory.createStartStateAssertion {},
-            LayerTraceEntrySubject::class,
-            AssertionTag.START
-        )
-        validate(
-            eventLogAssertionFactory.createStartStateAssertion {},
-            EventLogSubject::class,
-            AssertionTag.START
-        )
-    }
-
-    @Test
-    open fun checkBuildsEndAssertion() {
-        validate(
-            wmAssertionFactory.createEndStateAssertion {},
-            WindowManagerStateSubject::class,
-            AssertionTag.END
-        )
-        validate(
-            layersAssertionFactory.createEndStateAssertion {},
-            LayerTraceEntrySubject::class,
-            AssertionTag.END
-        )
-        validate(
-            eventLogAssertionFactory.createEndStateAssertion {},
-            EventLogSubject::class,
-            AssertionTag.END
-        )
-    }
-
-    @Test
-    open fun checkBuildsTagAssertion() {
-        validate(
-            wmAssertionFactory.createTagAssertion(TAG) {},
-            WindowManagerStateSubject::class,
-            TAG
-        )
-        validate(
-            layersAssertionFactory.createTagAssertion(TAG) {},
-            LayerTraceEntrySubject::class,
-            TAG
-        )
-        validate(eventLogAssertionFactory.createTagAssertion(TAG) {}, EventLogSubject::class, TAG)
-    }
-
-    protected fun validate(
-        assertionData: AssertionData,
-        expectedSubject: KClass<*>,
-        expectedTag: String
-    ) {
-        Truth.assertWithMessage("Subject")
-            .that(assertionData.expectedSubjectClass)
-            .isEqualTo(expectedSubject)
-        Truth.assertWithMessage("Tag").that(assertionData.tag).isEqualTo(expectedTag)
-    }
-
-    companion object {
-        internal const val TAG = "tag"
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionsCheckerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionsCheckerTest.kt
deleted file mode 100644
index 0d4285a..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/AssertionsCheckerTest.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertFailureFact
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ITraceEntry
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.assertions.AssertionsChecker
-import com.android.server.wm.traces.common.assertions.Fact
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [AssertionsChecker] tests. To run this test: `atest
- * FlickerLibTest:AssertionsCheckerTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AssertionsCheckerTest {
-    @Test
-    fun emptyRangePasses() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.test(emptyList())
-    }
-
-    @Test
-    fun canCheckChangingAssertions() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsIgnoreOptionalStart() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsIgnoreOptionalEnd() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsIgnoreOptionalMiddle() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData0") { it.isData0() }
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsIgnoreOptionalMultiple() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData0") { it.isData0() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.add("isData1", isOptional = true) { it.isData1() }
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsWithNoAssertions() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.test(getTestEntries(42, 0, 0, 0, 0))
-    }
-
-    @Test
-    fun canCheckChangingAssertionsWithSingleAssertion() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.test(getTestEntries(42, 42, 42, 42, 42))
-    }
-
-    @Test
-    fun canFailCheckChangingAssertionsIfStartingAssertionFails() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        val failure =
-            assertThrows<FlickerSubjectException> { checker.test(getTestEntries(0, 0, 0, 0, 0)) }
-        assertFailureFact(failure, "Assertion failed").isEqualTo("data is 42")
-    }
-
-    @Test
-    fun canCheckChangingAssertionsSkipUntilFirstSuccess() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.skipUntilFirstAssertion()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        checker.test(getTestEntries(0, 42, 0, 0, 0))
-    }
-
-    @Test
-    fun canFailCheckChangingAssertionsIfStartingAssertionAlwaysPasses() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42") { it.isData42() }
-        checker.add("isData0") { it.isData0() }
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                checker.test(getTestEntries(42, 42, 42, 42, 42))
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("Assertion never failed: isData42")
-    }
-
-    @Test
-    fun canFailCheckChangingAssertionsIfUsingCompoundAssertion() {
-        val checker = AssertionsChecker<SimpleEntrySubject>()
-        checker.add("isData42/0") { it.isData42().isData0() }
-        val failure =
-            assertThrows<FlickerSubjectException> { checker.test(getTestEntries(0, 0, 0, 0, 0)) }
-        assertFailureFact(failure, "Assertion failed").isEqualTo("data is 42")
-    }
-
-    private class SimpleEntrySubject(private val entry: SimpleEntry) : FlickerSubject() {
-        override val timestamp = CrossPlatform.timestamp.empty()
-        override val parent = null
-        override val selfFacts = listOf(Fact("SimpleEntry", entry.mData.toString()))
-
-        fun isData42() = apply { check { "data is 42" }.that(entry.mData).isEqual(42) }
-
-        fun isData0() = apply { check { "data is 0" }.that(entry.mData).isEqual(0) }
-
-        fun isData1() = apply { check { "data is 1" }.that(entry.mData).isEqual(1) }
-
-        companion object {
-            /** User-defined entry point */
-            @JvmStatic
-            fun assertThat(entry: SimpleEntry): SimpleEntrySubject {
-                return SimpleEntrySubject(entry)
-            }
-        }
-    }
-
-    data class SimpleEntry(override val timestamp: Timestamp, val mData: Int) : ITraceEntry
-
-    companion object {
-        /**
-         * Returns a list of SimpleEntry objects with `data` and incremental timestamps starting at
-         * 0.
-         */
-        private fun getTestEntries(vararg data: Int): List<SimpleEntrySubject> =
-            data.indices.map {
-                SimpleEntrySubject(
-                    SimpleEntry(CrossPlatform.timestamp.from(elapsedNanos = it.toLong()), data[it])
-                )
-            }
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/BaseSubjectsParserTestParse.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/BaseSubjectsParserTestParse.kt
deleted file mode 100644
index 14be12a..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/BaseSubjectsParserTestParse.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-abstract class BaseSubjectsParserTestParse {
-    protected abstract val assetFile: File
-    protected abstract val subjectName: String
-    protected abstract val expectedStartTime: Timestamp
-    protected abstract val expectedEndTime: Timestamp
-    protected abstract val traceType: TraceType
-
-    protected abstract fun getTime(timestamp: Timestamp): Long
-
-    protected abstract fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>?
-
-    protected abstract fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject?
-
-    protected open fun writeTrace(writer: ResultWriter): ResultWriter {
-        writer.addTraceResult(traceType, assetFile)
-        return writer
-    }
-
-    @Before
-    fun setup() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-    }
-
-    @Test
-    fun parseTraceSubject() {
-        val writer = writeTrace(newTestResultWriter())
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val parser = TestSubjectsParser(reader)
-        val subject = doParseTrace(parser) ?: error("$subjectName not built")
-
-        Truth.assertWithMessage(subjectName).that(subject.subjects).isNotEmpty()
-        Truth.assertWithMessage("$subjectName start")
-            .that(getTime(subject.subjects.first().timestamp))
-            .isEqualTo(getTime(expectedStartTime))
-        Truth.assertWithMessage("$subjectName end")
-            .that(getTime(subject.subjects.last().timestamp))
-            .isEqualTo(getTime(expectedEndTime))
-    }
-
-    @Test
-    fun parseStateSubjectTagStart() {
-        doParseStateSubjectAndValidate(AssertionTag.START, expectedStartTime)
-    }
-
-    @Test
-    fun parseStateSubjectTagEnd() {
-        doParseStateSubjectAndValidate(AssertionTag.END, expectedEndTime)
-    }
-
-    @Test
-    fun readTraceNullWhenDoesNotExist() {
-        val writer = newTestResultWriter()
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val parser = TestSubjectsParser(reader)
-        val subject = doParseTrace(parser)
-
-        Truth.assertWithMessage(subjectName).that(subject).isNull()
-    }
-
-    private fun doParseStateSubjectAndValidate(tag: String, expectedTime: Timestamp) {
-        val writer = writeTrace(newTestResultWriter())
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val parser = TestSubjectsParser(reader)
-        val subject = doParseState(parser, tag) ?: error("$subjectName tag=$tag not built")
-
-        Truth.assertWithMessage("$subjectName - $tag")
-            .that(getTime(subject.timestamp))
-            .isEqualTo(getTime(expectedTime))
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/Consts.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/Consts.kt
deleted file mode 100644
index 70281b0..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/Consts.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.flicker.assertions
-
-object Consts {
-    internal const val FAILURE = "Expected failure"
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTest.kt
deleted file mode 100644
index b65fee3..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTest.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.io.RunStatus
-import java.io.FileNotFoundException
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [SubjectsParser] */
-class SubjectsParserTest {
-
-    @Test
-    fun failFileNotFound() {
-        val data = newTestResultWriter().write()
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        val parser = SubjectsParser(ResultReader(data, DEFAULT_TRACE_CONFIG))
-        assertThrows<FileNotFoundException> { parser.getSubjects(AssertionTag.ALL) }
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseLayers.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseLayers.kt
deleted file mode 100644
index bc7a7ac..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseLayers.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import android.annotation.SuppressLint
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-
-@SuppressLint("VisibleForTests")
-class SubjectsParserTestParseLayers : BaseSubjectsParserTestParse() {
-    override val assetFile = TestTraces.LayerTrace.FILE
-    override val expectedStartTime = TestTraces.LayerTrace.START_TIME
-    override val expectedEndTime = TestTraces.LayerTrace.END_TIME
-    override val subjectName = "SF Trace"
-    override val traceType = TraceType.SF
-
-    override fun getTime(timestamp: Timestamp) = timestamp.systemUptimeNanos
-
-    override fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>? =
-        parser.doGetLayersTraceSubject()
-
-    override fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject? =
-        parser.doGetLayerTraceEntrySubject(tag)
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseWM.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseWM.kt
deleted file mode 100644
index db72799..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/SubjectsParserTestParseWM.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import android.annotation.SuppressLint
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerTraceSubject
-
-@SuppressLint("VisibleForTests")
-class SubjectsParserTestParseWM : BaseSubjectsParserTestParse() {
-    override val assetFile = TestTraces.WMTrace.FILE
-    override val expectedStartTime = TestTraces.WMTrace.START_TIME
-    override val expectedEndTime = TestTraces.WMTrace.END_TIME
-    override val subjectName = "WM Trace"
-    override val traceType = TraceType.WM
-
-    override fun getTime(timestamp: Timestamp) = timestamp.elapsedNanos
-
-    override fun doParseTrace(parser: TestSubjectsParser): FlickerTraceSubject<*>? =
-        parser.doGetWmTraceSubject()
-
-    override fun doParseState(parser: TestSubjectsParser, tag: String): FlickerSubject? =
-        parser.doGetWmStateSubject(tag)
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/TestSubjectsParser.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/TestSubjectsParser.kt
deleted file mode 100644
index 75c21f0..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/assertions/TestSubjectsParser.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.flicker.assertions
-
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.traces.common.assertions.SubjectsParser
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-
-/** Wrapper for [SubjectsParser] with looser visibility */
-class TestSubjectsParser(resultReader: ResultReader) : SubjectsParser(resultReader) {
-    public override fun doGetEventLogSubject(): EventLogSubject? = super.doGetEventLogSubject()
-
-    public override fun doGetWmTraceSubject(): WindowManagerTraceSubject? =
-        super.doGetWmTraceSubject()
-
-    public override fun doGetLayersTraceSubject(): LayersTraceSubject? =
-        super.doGetLayersTraceSubject()
-
-    public override fun doGetLayerTraceEntrySubject(tag: String): LayerTraceEntrySubject? =
-        super.doGetLayerTraceEntrySubject(tag)
-
-    public override fun doGetWmStateSubject(tag: String): WindowManagerStateSubject? =
-        super.doGetWmStateSubject(tag)
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedAssertionRunnerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedAssertionRunnerTest.kt
deleted file mode 100644
index 9ab4ca7..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedAssertionRunnerTest.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.monitor.EventLogMonitor
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.assertions.AssertionData
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.eventlog.EventLogSubject
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [CachedAssertionRunner] */
-@SuppressLint("VisibleForTests")
-class CachedAssertionRunnerTest {
-    private var executionCount = 0
-
-    private val assertionSuccess = newAssertionData { executionCount++ }
-    private val assertionFailure = newAssertionData {
-        executionCount++
-        error(com.android.server.wm.flicker.assertions.Consts.FAILURE)
-    }
-
-    @Before
-    fun setup() {
-        executionCount = 0
-        DataStore.clear()
-        val writer = newTestResultWriter()
-        val monitor = EventLogMonitor()
-        monitor.start()
-        monitor.stop(writer)
-        val result = writer.write()
-        DataStore.addResult(TEST_SCENARIO, result)
-    }
-
-    @Test
-    fun executes() {
-        val runner = CachedAssertionRunner(TEST_SCENARIO)
-        val firstAssertionResult = runner.runAssertion(assertionSuccess)
-        val lastAssertionResult = runner.runAssertion(assertionSuccess)
-        val result = DataStore.getResult(TEST_SCENARIO)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
-        Truth.assertWithMessage("Expected exception").that(firstAssertionResult).isNull()
-        Truth.assertWithMessage("Expected exception").that(lastAssertionResult).isNull()
-    }
-
-    @Test
-    fun executesFailure() {
-        val runner = CachedAssertionRunner(TEST_SCENARIO)
-        val firstAssertionResult = runner.runAssertion(assertionFailure)
-        val lastAssertionResult = runner.runAssertion(assertionFailure)
-        val result = DataStore.getResult(TEST_SCENARIO)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-
-        assertExceptionMessage(firstAssertionResult, Consts.FAILURE)
-        assertExceptionMessage(lastAssertionResult, Consts.FAILURE)
-        Truth.assertWithMessage("Same exception")
-            .that(firstAssertionResult)
-            .hasMessageThat()
-            .isEqualTo(lastAssertionResult?.message)
-    }
-
-    @Test
-    fun updatesRunStatusFailureFirst() {
-        val runner = CachedAssertionRunner(TEST_SCENARIO)
-        val firstAssertionResult = runner.runAssertion(assertionFailure)
-        val lastAssertionResult = runner.runAssertion(assertionSuccess)
-        val result = DataStore.getResult(TEST_SCENARIO)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        assertExceptionMessage(firstAssertionResult, Consts.FAILURE)
-        Truth.assertWithMessage("Expected exception").that(lastAssertionResult).isNull()
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-    }
-
-    @Test
-    fun updatesRunStatusFailureLast() {
-        val runner = CachedAssertionRunner(TEST_SCENARIO)
-        val firstAssertionResult = runner.runAssertion(assertionSuccess)
-        val lastAssertionResult = runner.runAssertion(assertionFailure)
-        val result = DataStore.getResult(TEST_SCENARIO)
-
-        Truth.assertWithMessage("Executed").that(executionCount).isEqualTo(2)
-        Truth.assertWithMessage("Expected exception").that(firstAssertionResult).isNull()
-        assertExceptionMessage(lastAssertionResult, Consts.FAILURE)
-        Truth.assertWithMessage("Run status")
-            .that(result.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-    }
-
-    companion object {
-        private fun newAssertionData(assertion: (FlickerSubject) -> Unit) =
-            AssertionData(AssertionTag.ALL, EventLogSubject::class, assertion)
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultReaderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultReaderTest.kt
deleted file mode 100644
index 05fe830..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultReaderTest.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [CachedResultReaderTest] */
-class CachedResultReaderTest {
-    @Before
-    fun setup() {
-        DataStore.clear()
-    }
-
-    @Test
-    fun readFromStore() {
-        val writer = newTestResultWriter()
-        writer.addTraceResult(TraceType.EVENT_LOG, TestTraces.EventLog.FILE)
-        val result = writer.write()
-        DataStore.addResult(TEST_SCENARIO, result)
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        val actual = reader.readEventLogTrace()
-        Truth.assertWithMessage("Event log size").that(actual).isNotNull()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultWriterTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultWriterTest.kt
deleted file mode 100644
index 97754d0..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/CachedResultWriterTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.newTestCachedResultWriter
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [CachedResultWriterTest] */
-@SuppressLint("VisibleForTests")
-class CachedResultWriterTest {
-    @Before
-    fun setup() {
-        DataStore.clear()
-    }
-
-    @Test
-    fun writeToStore() {
-        val writer = newTestCachedResultWriter()
-        val expected = writer.write()
-        Truth.assertWithMessage("Has key in store")
-            .that(DataStore.containsResult(TEST_SCENARIO))
-            .isTrue()
-        val actual = DataStore.getResult(TEST_SCENARIO)
-        Truth.assertWithMessage("Has key in store").that(expected).isEqualTo(actual)
-    }
-
-    @Test
-    fun writeToStoreFailsWhenWriteTwice() {
-        val writer = newTestCachedResultWriter()
-        val failure =
-            assertThrows<IllegalStateException> {
-                writer.write()
-                writer.write()
-            }
-        Truth.assertWithMessage("Has key in store")
-            .that(DataStore.containsResult(TEST_SCENARIO))
-            .isTrue()
-        assertExceptionMessage(failure, TEST_SCENARIO.toString())
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/Consts.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/Consts.kt
deleted file mode 100644
index 2a8544f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/Consts.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.io.ResultData
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TransitionTimeRange
-
-object Consts {
-    internal const val FAILURE = "Expected failure"
-
-    internal val TEST_RESULT =
-        ResultData(
-            _artifact = getDefaultFlickerOutputDir(),
-            _transitionTimeRange =
-                TransitionTimeRange(
-                    CrossPlatform.timestamp.empty(),
-                    CrossPlatform.timestamp.empty()
-                ),
-            _executionError = null,
-            _runStatus = RunStatus.RUN_EXECUTED
-        )
-
-    internal val RESULT_FAILURE =
-        ResultData(
-            _artifact = getDefaultFlickerOutputDir(),
-            _transitionTimeRange =
-                TransitionTimeRange(
-                    CrossPlatform.timestamp.empty(),
-                    CrossPlatform.timestamp.empty()
-                ),
-            _executionError = null,
-            _runStatus = RunStatus.RUN_EXECUTED
-        )
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/DataStoreTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/DataStoreTest.kt
deleted file mode 100644
index 45c9958..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/datastore/DataStoreTest.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.flicker.datastore
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.assertThrows
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-@SuppressLint("VisibleForTests")
-class DataStoreTest {
-    @Before
-    fun setup() {
-        DataStore.clear()
-    }
-
-    @Test
-    fun addsElement() {
-        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
-        Truth.assertWithMessage("Contains result")
-            .that(DataStore.containsResult(TEST_SCENARIO))
-            .isTrue()
-    }
-
-    @Test
-    fun throwsErrorAddElementTwice() {
-        val failure =
-            assertThrows<IllegalStateException> {
-                DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
-                DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
-            }
-        Truth.assertWithMessage("Contains result")
-            .that(DataStore.containsResult(TEST_SCENARIO))
-            .isTrue()
-        assertExceptionMessage(failure, TEST_SCENARIO.toString())
-    }
-
-    @Test
-    fun getsElement() {
-        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
-        val actual = DataStore.getResult(TEST_SCENARIO)
-        Truth.assertWithMessage("Expected result").that(actual).isEqualTo(Consts.TEST_RESULT)
-    }
-
-    @Test
-    fun getsElementThrowErrorDoesNotExist() {
-        val failure = assertThrows<IllegalStateException> { DataStore.getResult(TEST_SCENARIO) }
-        assertExceptionMessage(failure, TEST_SCENARIO.toString())
-    }
-
-    @Test
-    fun replacesElement() {
-        DataStore.addResult(TEST_SCENARIO, Consts.TEST_RESULT)
-        DataStore.replaceResult(TEST_SCENARIO, Consts.RESULT_FAILURE)
-        val actual = DataStore.getResult(TEST_SCENARIO)
-        Truth.assertWithMessage("Expected value").that(actual).isEqualTo(Consts.RESULT_FAILURE)
-    }
-
-    @Test
-    fun replacesElementThrowErrorDoesNotExist() {
-        val failure =
-            assertThrows<IllegalStateException> {
-                DataStore.replaceResult(TEST_SCENARIO, Consts.RESULT_FAILURE)
-            }
-        assertExceptionMessage(failure, TEST_SCENARIO.toString())
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/helpers/TimeUtilsTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/helpers/TimeUtilsTest.kt
deleted file mode 100644
index ae067cc..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/helpers/TimeUtilsTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.helpers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.Utils
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Contains [Utils] formatting tests. To run this test: `atest FlickerLibTest:TimeUtilsTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TimeUtilsTest {
-    @Test
-    fun canFormatElapsedTime() {
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(0)).isEqualTo("0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(1000)).isEqualTo("1000ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MILLISECOND - 1)).isEqualTo("999999ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MILLISECOND)).isEqualTo("1ms0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(10 * MILLISECOND)).isEqualTo("10ms0ns")
-
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND - 1)).isEqualTo("999ms999999ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND)).isEqualTo("1s0ms0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(SECOND + MILLISECOND))
-            .isEqualTo("1s1ms0ns")
-
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE - 1)).isEqualTo("59s999ms999999ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE)).isEqualTo("1m0s0ms0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE + SECOND + MILLISECOND))
-            .isEqualTo("1m1s1ms0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1))
-            .isEqualTo("1m1s1ms1ns")
-
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR - 1))
-            .isEqualTo("59m59s999ms999999ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR)).isEqualTo("1h0m0s0ms0ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND))
-            .isEqualTo("1h1m1s1ms0ns")
-
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(DAY - 1))
-            .isEqualTo("23h59m59s999ms999999ns")
-        Truth.assertThat(Timestamp.formatElapsedTimestamp(DAY)).isEqualTo("1d0h0m0s0ms0ns")
-        Truth.assertThat(
-                Timestamp.formatElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND)
-            )
-            .isEqualTo("1d1h1m1s1ms0ns")
-    }
-
-    @Test
-    fun canFormatRealTime() {
-        Truth.assertThat(Utils.formatRealTimestamp(0)).isEqualTo("1970-01-01T00:00:00.000000000")
-        Truth.assertThat(
-                Utils.formatRealTimestamp(
-                    NOV_10_2022 + 22 * HOUR + 4 * MINUTE + 54 * SECOND + 186 * MILLISECOND + 123212
-                )
-            )
-            .isEqualTo("2022-11-10T22:04:54.186123212")
-        Truth.assertThat(
-                Utils.formatRealTimestamp(
-                    NOV_10_2022 + 22 * HOUR + 4 * MINUTE + 54 * SECOND + 186 * MILLISECOND + 2
-                )
-            )
-            .isEqualTo("2022-11-10T22:04:54.186000002")
-        Truth.assertThat(Utils.formatRealTimestamp(NOV_10_2022))
-            .isEqualTo("2022-11-10T00:00:00.000000000")
-        Truth.assertThat(Utils.formatRealTimestamp(NOV_10_2022 + 1))
-            .isEqualTo("2022-11-10T00:00:00.000000001")
-    }
-
-    @Test
-    fun formatToRightType() {
-        Truth.assertThat(CrossPlatform.timestamp.from(unixNanos = 1668117894186123212L).toString())
-            .startsWith("2022-11-10T22:04:54.186123212")
-        Truth.assertThat(
-                CrossPlatform.timestamp.from(elapsedNanos = 10 * DAY + 12 * HOUR).toString()
-            )
-            .startsWith("10d12h0m0s0ms0ns")
-    }
-
-    companion object {
-        private const val MILLISECOND = 1000000L
-        private const val SECOND = 1000 * MILLISECOND
-        private const val MINUTE = 60 * SECOND
-        private const val HOUR = 60 * MINUTE
-        private const val DAY = 24 * HOUR
-        private const val NOV_10_2022 = 1668038400000 * MILLISECOND
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/AssertionErrorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/integration/AssertionErrorTest.kt
deleted file mode 100644
index b022e48..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/AssertionErrorTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.flicker.integration
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.traces.common.io.RunStatus
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Integration tests to ensure assertions fail correctly
- *
- * To run this test: `atest FlickerLibTest:AssertionErrorTest`
- */
-class AssertionErrorTest {
-    private var assertionExecuted = false
-    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
-
-    @Before
-    fun setup() {
-        assertionExecuted = false
-    }
-
-    @Test
-    fun executesTransition() {
-        Truth.assertWithMessage("Transition executed").that(transitionExecuted).isTrue()
-        assertArtifactExists()
-    }
-
-    @Test
-    fun assertThrowsAssertionError() {
-        val result = runCatching {
-            testParam.assertLayers {
-                assertionExecuted = true
-                error(Utils.FAILURE)
-            }
-        }
-
-        Truth.assertWithMessage("Executed").that(assertionExecuted).isTrue()
-        Truth.assertWithMessage("Expected exception").that(result.isSuccess).isFalse()
-        Truth.assertWithMessage("Expected exception")
-            .that(result.exceptionOrNull())
-            .hasMessageThat()
-            .contains(Utils.FAILURE)
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Run status")
-            .that(reader.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_FAILED)
-        assertArtifactExists()
-    }
-
-    private fun assertArtifactExists() {
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        val file = File(reader.artifactPath)
-        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
-    }
-
-    companion object {
-        private var transitionExecuted = false
-        @BeforeClass
-        @JvmStatic
-        fun runTransition() = Utils.runTransition { transitionExecuted = true }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/FullTestRun.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/integration/FullTestRun.kt
deleted file mode 100644
index 66c6a03..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/FullTestRun.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.flicker.integration
-
-import android.app.Instrumentation
-import android.platform.test.annotations.Presubmit
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.helpers.CalculatorAppHelper
-import com.android.server.wm.flicker.junit.FlickerBuilderProvider
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.server.wm.traces.common.Scenario
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class FullTestRun(private val flicker: FlickerTest) {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp: CalculatorAppHelper = CalculatorAppHelper(instrumentation)
-    private val tapl: LauncherInstrumentation = LauncherInstrumentation()
-
-    init {
-        flicker.scenario.setIsTablet(
-            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
-                .currentState
-                .wmState
-                .isTablet
-        )
-        tapl.setExpectedRotationCheckEnabled(true)
-    }
-
-    /**
-     * Entry point for the test runner. It will use this method to initialize and cache flicker
-     * executions
-     */
-    @FlickerBuilderProvider
-    fun buildFlicker(): FlickerBuilder {
-        return FlickerBuilder(instrumentation).apply {
-            setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-    }
-
-    /**
-     * This is a shel test from the flicker infra to ensure the WM tracing pipeline executed
-     * entirely executed correctly
-     */
-    @Presubmit
-    @Test
-    fun internalWmCheck() {
-        var trace: WindowManagerTraceSubject? = null
-        var executionCount = 0
-        flicker.assertWm {
-            executionCount++
-            trace = this
-            this.isNotEmpty()
-        }
-        flicker.assertWm {
-            executionCount++
-            val failure: Result<Any> = runCatching { this.isEmpty() }
-            if (failure.isSuccess) {
-                error("Should have thrown failure")
-            }
-        }
-        flicker.assertWmStart {
-            executionCount++
-            validateState(this, trace?.first())
-            validateVisibleRegion(this.visibleRegion(), trace?.first()?.visibleRegion())
-        }
-        flicker.assertWmEnd {
-            executionCount++
-            validateState(this, trace?.last())
-            validateVisibleRegion(this.visibleRegion(), trace?.last()?.visibleRegion())
-        }
-        Truth.assertWithMessage("Execution count").that(executionCount).isEqualTo(4)
-    }
-
-    /**
-     * This is a shel test from the flicker infra to ensure the Layers tracing pipeline executed
-     * entirely executed correctly
-     */
-    @Presubmit
-    @Test
-    fun internalLayersCheck() {
-        var trace: LayersTraceSubject? = null
-        var executionCount = 0
-        flicker.assertLayers {
-            executionCount++
-            trace = this
-            this.isNotEmpty()
-        }
-        flicker.assertLayers {
-            executionCount++
-            val failure: Result<Any> = runCatching { this.isEmpty() }
-            if (failure.isSuccess) {
-                error("Should have thrown failure")
-            }
-        }
-        flicker.assertLayersStart {
-            executionCount++
-            validateState(this, trace?.first())
-            validateVisibleRegion(this.visibleRegion(), trace?.first()?.visibleRegion())
-        }
-        flicker.assertLayersEnd {
-            executionCount++
-            validateState(this, trace?.last())
-            validateVisibleRegion(this.visibleRegion(), trace?.last()?.visibleRegion())
-        }
-        Truth.assertWithMessage("Execution count").that(executionCount).isEqualTo(4)
-    }
-
-    private fun validateState(actual: FlickerSubject?, expected: FlickerSubject?) {
-        Truth.assertWithMessage("Actual state").that(actual).isNotNull()
-        Truth.assertWithMessage("Expected state").that(expected).isNotNull()
-        Truth.assertWithMessage("Incorrect state")
-            .that(actual?.completeFacts?.joinToString { it.toString() })
-            .isEqualTo(expected?.completeFacts?.joinToString { it.toString() })
-    }
-
-    private fun validateVisibleRegion(
-        actual: RegionSubject?,
-        expected: RegionSubject?,
-    ) {
-        Truth.assertWithMessage("Actual visible region").that(actual).isNotNull()
-        Truth.assertWithMessage("Expected visible region").that(expected).isNotNull()
-        actual?.coversExactly(expected?.region ?: Region.EMPTY)
-
-        val failure: Result<Any?> = runCatching {
-            actual?.isHigher(expected?.region ?: Region.EMPTY)
-        }
-        if (failure.isSuccess) {
-            error("Should have thrown failure")
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                extraArgs = mapOf(Scenario.FAAS_BLOCKING to true)
-            )
-        }
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/NoErrorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/integration/NoErrorTest.kt
deleted file mode 100644
index f0bb35f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/NoErrorTest.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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.flicker.integration
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.traces.common.io.RunStatus
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.mockito.junit.MockitoJUnitRunner
-
-/**
- * Contains an integration test
- *
- * To run this test: `atest FlickerLibTest:IntegrationTests`
- */
-@RunWith(MockitoJUnitRunner::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NoErrorTest {
-    private var assertionExecuted = false
-    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
-
-    @Before
-    fun setup() {
-        assertionExecuted = false
-    }
-
-    @Test
-    fun executesTransition() {
-        Truth.assertWithMessage("Transition executed").that(transitionExecuted).isTrue()
-    }
-
-    @Test
-    fun assertSetupAndTearDownTestAppNeverExists() {
-        assertPredicatePasses {
-            testParam.assertWm {
-                assertionExecuted = true
-                require(
-                    this.subjects.none {
-                        it.wmState
-                            .getActivitiesForWindow(Utils.setupAndTearDownTestApp.componentMatcher)
-                            .isNotEmpty()
-                    }
-                ) {
-                    "${Utils.setupAndTearDownTestApp.appName} window existed at some point " +
-                        "but shouldn't have."
-                }
-            }
-        }
-    }
-
-    @Test
-    fun assertTransitionTestAppExistsAtSomePoint() {
-        assertPredicatePasses {
-            testParam.assertWm {
-                assertionExecuted = true
-                require(
-                    this.subjects.any {
-                        it.wmState
-                            .getActivitiesForWindow(Utils.transitionTestApp.componentMatcher)
-                            .isNotEmpty()
-                    }
-                ) {
-                    "${Utils.transitionTestApp.appName} window didn't exist at any point " +
-                        "but should have."
-                }
-            }
-        }
-    }
-
-    @Test
-    fun assertSetupAndTearDownTestLayerNeverExists() {
-        assertPredicatePasses {
-            testParam.assertLayers {
-                assertionExecuted = true
-                require(
-                    this.subjects.none {
-                        Utils.setupAndTearDownTestApp.componentMatcher.layerMatchesAnyOf(
-                            it.entry.flattenedLayers.filter { layer -> layer.isVisible }
-                        )
-                    }
-                ) {
-                    "${Utils.setupAndTearDownTestApp.appName} layer was visible at some point " +
-                        "but shouldn't have."
-                }
-
-                require(
-                    this.subjects.none {
-                        Utils.setupAndTearDownTestApp.componentMatcher.layerMatchesAnyOf(
-                            it.entry.flattenedLayers
-                        )
-                    }
-                ) {
-                    "${Utils.setupAndTearDownTestApp.appName} layer existed at some point " +
-                        "but shouldn't have."
-                }
-            }
-        }
-    }
-
-    @Test
-    fun assertTransitionTestLayerExistsAtSomePoint() {
-        assertPredicatePasses {
-            testParam.assertLayers {
-                assertionExecuted = true
-                require(
-                    this.subjects.any {
-                        Utils.transitionTestApp.componentMatcher.layerMatchesAnyOf(
-                            it.entry.flattenedLayers
-                        )
-                    }
-                ) {
-                    "${Utils.transitionTestApp.appName} layer didn't exist at any point " +
-                        "but should have."
-                }
-            }
-        }
-    }
-
-    @Test
-    fun assertStartStateLayersIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertLayersStart {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertEndStateLayersIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertLayersEnd {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertStartStateWmIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertWmStart {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertEndStateWmIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertWmEnd {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertTagStateLayersIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertLayersTag(Utils.TAG) {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertTagStateWmIsNotEmpty() {
-        assertPredicatePasses {
-            testParam.assertWmTag(Utils.TAG) {
-                assertionExecuted = true
-                isNotEmpty()
-            }
-        }
-    }
-
-    @Test
-    fun assertEventLog() {
-        assertPredicatePasses { testParam.assertEventLog { assertionExecuted = true } }
-    }
-
-    private fun assertPredicatePasses(predicate: () -> Unit) {
-        predicate.invoke()
-        Truth.assertWithMessage("Executed").that(assertionExecuted).isTrue()
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Run status")
-            .that(reader.runStatus)
-            .isEqualTo(RunStatus.ASSERTION_SUCCESS)
-        assertArtifactExists()
-    }
-
-    private fun assertArtifactExists() {
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        val file = File(reader.artifactPath)
-        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
-    }
-
-    companion object {
-        private var transitionExecuted = false
-        @BeforeClass
-        @JvmStatic
-        fun runTransition() = Utils.runTransition { transitionExecuted = true }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/TransitionErrorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/integration/TransitionErrorTest.kt
deleted file mode 100644
index 0f2fef9..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/TransitionErrorTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.flicker.integration
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.datastore.CachedResultReader
-import com.android.server.wm.traces.common.io.RunStatus
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-class TransitionErrorTest {
-    private var assertionExecuted = false
-    private val testParam = FlickerTest().also { it.initialize(TEST_SCENARIO.testClass) }
-
-    @Before
-    fun setup() {
-        assertionExecuted = false
-    }
-
-    @Test
-    fun failsToExecuteTransition() {
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertArtifactExists()
-    }
-
-    @Test
-    fun assertThrowsTransitionError() {
-        val results =
-            (0..10).map {
-                runCatching {
-                    testParam.assertLayers {
-                        assertionExecuted = true
-                        error(WRONG_EXCEPTION)
-                    }
-                }
-            }
-
-        Truth.assertWithMessage("Executed").that(assertionExecuted).isFalse()
-        results.forEach { result ->
-            Truth.assertWithMessage("Expected exception").that(result.isFailure).isTrue()
-            Truth.assertWithMessage("Expected exception")
-                .that(result.exceptionOrNull())
-                .hasMessageThat()
-                .contains(Utils.FAILURE)
-        }
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertArtifactExists()
-    }
-
-    private fun assertArtifactExists() {
-        val reader = CachedResultReader(TEST_SCENARIO, DEFAULT_TRACE_CONFIG)
-        val file = File(reader.artifactPath)
-        Truth.assertWithMessage("Files exist").that(file.exists()).isTrue()
-    }
-
-    companion object {
-        private const val WRONG_EXCEPTION = "Wrong exception"
-
-        @BeforeClass @JvmStatic fun runTransition() = Utils.runTransition { error(Utils.FAILURE) }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/Utils.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/integration/Utils.kt
deleted file mode 100644
index f69ba7f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/integration/Utils.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.flicker.integration
-
-import android.annotation.SuppressLint
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.helpers.BrowserAppHelper
-import com.android.server.wm.flicker.helpers.MessagingAppHelper
-import com.android.server.wm.flicker.runner.TransitionRunner
-import org.junit.runner.Description
-
-@SuppressLint("VisibleForTests")
-object Utils {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    internal const val TAG = "tag"
-    internal const val FAILURE = "Expected failure"
-    internal val setupAndTearDownTestApp = BrowserAppHelper(instrumentation)
-    internal val transitionTestApp = MessagingAppHelper(instrumentation)
-
-    private fun createFlicker(onExecuted: () -> Unit) =
-        FlickerBuilder(instrumentation)
-            .apply {
-                setup {
-                    // Shouldn't be in the trace we run assertions on
-                    setupAndTearDownTestApp.launchViaIntent(wmHelper)
-                    setupAndTearDownTestApp.exit(wmHelper)
-                }
-                transitions {
-                    // Should be in the trace we run assertions on
-                    transitionTestApp.launchViaIntent(wmHelper)
-                    createTag(TAG)
-                    onExecuted()
-                }
-                teardown {
-                    // Shouldn't be in the trace we run assertions on
-                    setupAndTearDownTestApp.launchViaIntent(wmHelper)
-                    setupAndTearDownTestApp.exit(wmHelper)
-                }
-            }
-            .build()
-
-    fun runTransition(onExecuted: () -> Unit) {
-        DataStore.clear()
-        val flicker = createFlicker(onExecuted)
-
-        // Clear the trace output directory
-        SystemUtil.runShellCommand("rm -rf ${flicker.outputDir}")
-
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation)
-        runner.execute(flicker, Description.createTestDescription(this::class.java, "test"))
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/BaseResultReaderTestParseTrace.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/BaseResultReaderTestParseTrace.kt
deleted file mode 100644
index c495826..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/BaseResultReaderTestParseTrace.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ITrace
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Base class for [ResultReader] tests parsing traces */
-abstract class BaseResultReaderTestParseTrace {
-    protected abstract val assetFile: File
-    protected abstract val traceName: String
-    protected abstract val startTimeTrace: Timestamp
-    protected abstract val endTimeTrace: Timestamp
-    protected abstract val validSliceTime: Timestamp
-    protected abstract val invalidSliceTime: Timestamp
-    protected abstract val traceType: TraceType
-    protected abstract val expectedSlicedTraceSize: Int
-    protected open val invalidSizeMessage: String
-        get() = "$traceName contained 0 entries, expected at least 2"
-
-    protected abstract fun doParse(reader: ResultReader): ITrace<*>?
-    protected abstract fun getTime(traceTime: Timestamp): Long
-
-    protected open fun setupWriter(writer: ResultWriter): ResultWriter {
-        writer.addTraceResult(traceType, assetFile)
-        return writer
-    }
-
-    @Before
-    fun setup() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-    }
-
-    @Test
-    fun readTrace() {
-        val writer = setupWriter(newTestResultWriter())
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val trace = doParse(reader) ?: error("$traceName not built")
-
-        Truth.assertWithMessage(traceName).that(trace.entries).asList().isNotEmpty()
-        Truth.assertWithMessage("$traceName start")
-            .that(getTime(trace.entries.first().timestamp))
-            .isEqualTo(getTime(startTimeTrace))
-        Truth.assertWithMessage("$traceName end")
-            .that(getTime(trace.entries.last().timestamp))
-            .isEqualTo(getTime(endTimeTrace))
-    }
-
-    @Test
-    fun readTraceNullWhenDoesNotExist() {
-        val writer = newTestResultWriter()
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val trace = doParse(reader)
-
-        Truth.assertWithMessage(traceName).that(trace).isNull()
-    }
-
-    @Test
-    fun readTraceAndSliceTraceByTimestamp() {
-        val result =
-            setupWriter(newTestResultWriter())
-                .setTransitionStartTime(startTimeTrace)
-                .setTransitionEndTime(validSliceTime)
-                .write()
-        val reader = ResultReader(result, TestTraces.TEST_TRACE_CONFIG)
-        val trace = doParse(reader) ?: error("$traceName not built")
-
-        Truth.assertWithMessage(traceName)
-            .that(trace.entries)
-            .asList()
-            .hasSize(expectedSlicedTraceSize)
-        Truth.assertWithMessage("$traceName start")
-            .that(getTime(trace.entries.first().timestamp))
-            .isEqualTo(getTime(startTimeTrace))
-    }
-
-    @Test
-    fun readTraceAndSliceTraceByTimestampAndFailInvalidSize() {
-        val result =
-            setupWriter(newTestResultWriter())
-                .setTransitionEndTime(CrossPlatform.timestamp.min())
-                .write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val exception =
-            assertThrows<IllegalArgumentException> {
-                doParse(reader) ?: error("$traceName not built")
-            }
-        assertExceptionMessage(exception, invalidSizeMessage)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ParsedTracesReader.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ParsedTracesReader.kt
deleted file mode 100644
index 01fd067..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ParsedTracesReader.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.events.CujTrace
-import com.android.server.wm.traces.common.events.EventLog
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.transactions.TransactionsTrace
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-/** Reads parsed traces from in memory objects */
-class ParsedTracesReader(
-    private val wmTrace: WindowManagerTrace? = null,
-    private val layersTrace: LayersTrace? = null,
-    private val transitionsTrace: TransitionsTrace? = null,
-    private val transactionsTrace: TransactionsTrace? = null,
-    private val eventLog: EventLog? = null
-) : IReader {
-    override val artifactPath = ""
-    override val runStatus = RunStatus.UNDEFINED
-    override val executionError = null
-
-    override fun readLayersTrace(): LayersTrace? = layersTrace
-
-    override fun readTransactionsTrace(): TransactionsTrace? = transactionsTrace
-
-    override fun readTransitionsTrace(): TransitionsTrace? = transitionsTrace
-
-    override fun readWmTrace(): WindowManagerTrace? = wmTrace
-
-    override fun readEventLogTrace(): EventLog? = eventLog
-
-    override fun readCujTrace(): CujTrace? = eventLog?.cujTrace
-
-    override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ParsedTracesReader {
-        return ParsedTracesReader(
-            wmTrace?.slice(startTimestamp, endTimestamp),
-            layersTrace?.slice(startTimestamp, endTimestamp),
-            transitionsTrace?.slice(startTimestamp, endTimestamp),
-            transactionsTrace?.slice(startTimestamp, endTimestamp),
-            eventLog?.slice(startTimestamp, endTimestamp)
-        )
-    }
-
-    override fun readLayersDump(tag: String): LayersTrace? {
-        error("Trace type not available")
-    }
-
-    override fun readWmState(tag: String): WindowManagerTrace? {
-        error("Trace type not available")
-    }
-
-    override fun readBytes(traceType: TraceType, tag: String): ByteArray? {
-        error("Feature not available")
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultArtifactDescriptorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultArtifactDescriptorTest.kt
deleted file mode 100644
index 746d270..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultArtifactDescriptorTest.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.AssertionTag
-import com.android.server.wm.traces.common.io.ResultArtifactDescriptor
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Tests for [ResultArtifactDescriptor] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ResultArtifactDescriptorTest {
-    @Test
-    fun generateDescriptorFromTrace() {
-        createDescriptorAndValidateFileName(TraceType.SF)
-        createDescriptorAndValidateFileName(TraceType.WM)
-        createDescriptorAndValidateFileName(TraceType.TRANSACTION)
-        createDescriptorAndValidateFileName(TraceType.TRANSACTION)
-        createDescriptorAndValidateFileName(TraceType.SCREEN_RECORDING)
-        createDescriptorAndValidateFileName(TraceType.WM_DUMP)
-        createDescriptorAndValidateFileName(TraceType.SF_DUMP)
-    }
-
-    @Test
-    fun generateDescriptorFromTraceWithTags() {
-        createDescriptorAndValidateFileNameWithTag(TraceType.SF)
-        createDescriptorAndValidateFileNameWithTag(TraceType.WM)
-        createDescriptorAndValidateFileNameWithTag(TraceType.TRANSACTION)
-        createDescriptorAndValidateFileNameWithTag(TraceType.TRANSACTION)
-        createDescriptorAndValidateFileNameWithTag(TraceType.SCREEN_RECORDING)
-        createDescriptorAndValidateFileNameWithTag(TraceType.WM_DUMP)
-        createDescriptorAndValidateFileNameWithTag(TraceType.SF_DUMP)
-    }
-
-    @Test
-    fun parseDescriptorFromFileName() {
-        parseDescriptorAndValidateType(TraceType.SF.fileName, TraceType.SF)
-        parseDescriptorAndValidateType(TraceType.WM.fileName, TraceType.WM)
-        parseDescriptorAndValidateType(TraceType.TRANSACTION.fileName, TraceType.TRANSACTION)
-        parseDescriptorAndValidateType(TraceType.TRANSACTION.fileName, TraceType.TRANSACTION)
-        parseDescriptorAndValidateType(
-            TraceType.SCREEN_RECORDING.fileName,
-            TraceType.SCREEN_RECORDING
-        )
-        parseDescriptorAndValidateType(TraceType.WM_DUMP.fileName, TraceType.WM_DUMP)
-        parseDescriptorAndValidateType(TraceType.SF_DUMP.fileName, TraceType.SF_DUMP)
-    }
-
-    @Test
-    fun parseDescriptorFromFileNameWithTags() {
-        parseDescriptorAndValidateType(buildTaggedName(TraceType.SF), TraceType.SF, TEST_TAG)
-        parseDescriptorAndValidateType(buildTaggedName(TraceType.WM), TraceType.WM, TEST_TAG)
-        parseDescriptorAndValidateType(
-            buildTaggedName(TraceType.TRANSACTION),
-            TraceType.TRANSACTION,
-            TEST_TAG
-        )
-        parseDescriptorAndValidateType(
-            buildTaggedName(TraceType.TRANSACTION),
-            TraceType.TRANSACTION,
-            TEST_TAG
-        )
-        parseDescriptorAndValidateType(
-            buildTaggedName(TraceType.SCREEN_RECORDING),
-            TraceType.SCREEN_RECORDING,
-            TEST_TAG
-        )
-        parseDescriptorAndValidateType(
-            buildTaggedName(TraceType.WM_DUMP),
-            TraceType.WM_DUMP,
-            TEST_TAG
-        )
-        parseDescriptorAndValidateType(
-            buildTaggedName(TraceType.SF_DUMP),
-            TraceType.SF_DUMP,
-            TEST_TAG
-        )
-    }
-
-    private fun buildTaggedName(traceType: TraceType): String =
-        ResultArtifactDescriptor(traceType, TEST_TAG).fileNameInArtifact
-
-    private fun parseDescriptorAndValidateType(
-        fileNameInArtifact: String,
-        expectedTraceType: TraceType,
-        expectedTag: String = AssertionTag.ALL
-    ): ResultArtifactDescriptor {
-        val descriptor = ResultArtifactDescriptor.fromFileName(fileNameInArtifact)
-        Truth.assertWithMessage("Descriptor type")
-            .that(descriptor.traceType)
-            .isEqualTo(expectedTraceType)
-        Truth.assertWithMessage("Descriptor tag").that(descriptor.tag).isEqualTo(expectedTag)
-        return descriptor
-    }
-
-    private fun createDescriptorAndValidateFileName(traceType: TraceType) {
-        val descriptor = ResultArtifactDescriptor(traceType)
-        Truth.assertWithMessage("Result file name")
-            .that(descriptor.fileNameInArtifact)
-            .isEqualTo(traceType.fileName)
-    }
-
-    private fun createDescriptorAndValidateFileNameWithTag(traceType: TraceType) {
-        val tag = "testTag"
-        val descriptor = ResultArtifactDescriptor(traceType, TEST_TAG)
-        val subject =
-            Truth.assertWithMessage("Result file name").that(descriptor.fileNameInArtifact)
-        subject.startsWith(tag)
-        subject.endsWith(traceType.fileName)
-    }
-
-    companion object {
-        private const val TEST_TAG = "testTag"
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTest.kt
deleted file mode 100644
index 22da2b7..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.io.RunStatus
-import java.io.FileNotFoundException
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Tests for [ResultReader] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ResultReaderTest {
-    @Before
-    fun setup() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-    }
-
-    @Test
-    fun failFileNotFound() {
-        val data = newTestResultWriter().write()
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        val reader = ResultReader(data, DEFAULT_TRACE_CONFIG)
-        assertThrows<FileNotFoundException> {
-            reader.readTransitionsTrace() ?: error("Should have failed")
-        }
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseEventLog.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseEventLog.kt
deleted file mode 100644
index 4683168..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseEventLog.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-import org.junit.FixMethodOrder
-import org.junit.runners.MethodSorters
-
-/** Tests for [ResultReader] parsing event log */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ResultReaderTestParseEventLog : BaseResultReaderTestParseTrace() {
-    override val assetFile = TestTraces.EventLog.FILE
-    override val traceName = "Event Log"
-    override val startTimeTrace = TestTraces.EventLog.START_TIME
-    override val endTimeTrace = TestTraces.EventLog.END_TIME
-    override val validSliceTime = TestTraces.EventLog.SLICE_TIME
-    override val invalidSliceTime = startTimeTrace
-    override val traceType = TraceType.EVENT_LOG
-    override val expectedSlicedTraceSize = 125
-    override val invalidSizeMessage: String = "'to' needs to be greater than 'from'"
-
-    override fun doParse(reader: ResultReader) = reader.readEventLogTrace()
-    override fun getTime(traceTime: Timestamp) = traceTime.unixNanos
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseLayers.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseLayers.kt
deleted file mode 100644
index 66344d7..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseLayers.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-
-/** Tests for [ResultReader] parsing [TraceType.SF] */
-class ResultReaderTestParseLayers : BaseResultReaderTestParseTrace() {
-    override val assetFile = TestTraces.LayerTrace.FILE
-    override val traceName = "Layers trace"
-    override val startTimeTrace = TestTraces.LayerTrace.START_TIME
-    override val endTimeTrace = TestTraces.LayerTrace.END_TIME
-    override val validSliceTime = TestTraces.LayerTrace.SLICE_TIME
-    override val invalidSliceTime = startTimeTrace
-    override val traceType = TraceType.SF
-    override val expectedSlicedTraceSize = 2
-
-    override fun doParse(reader: ResultReader) = reader.readLayersTrace()
-    override fun getTime(traceTime: Timestamp) = traceTime.systemUptimeNanos
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransactions.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransactions.kt
deleted file mode 100644
index 63e2c37..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransactions.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-
-/** Tests for [ResultReader] parsing [TraceType.TRANSACTION] */
-class ResultReaderTestParseTransactions : BaseResultReaderTestParseTrace() {
-    override val assetFile = TestTraces.TransactionTrace.FILE
-    override val traceName = "Transactions trace"
-    override val startTimeTrace = TestTraces.TransactionTrace.START_TIME
-    override val endTimeTrace = TestTraces.TransactionTrace.END_TIME
-    override val validSliceTime = TestTraces.TransactionTrace.VALID_SLICE_TIME
-    override val invalidSliceTime = TestTraces.TransactionTrace.INVALID_SLICE_TIME
-    override val traceType = TraceType.TRANSACTION
-    override val invalidSizeMessage = "Transactions trace cannot be empty"
-    override val expectedSlicedTraceSize = 2
-
-    override fun doParse(reader: ResultReader) = reader.readTransactionsTrace()
-    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransitions.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransitions.kt
deleted file mode 100644
index 6ab88e1..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseTransitions.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.flicker.readAssetAsFile
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-
-/** Tests for [ResultReader] parsing [TraceType.TRANSITION] */
-class ResultReaderTestParseTransitions : BaseResultReaderTestParseTrace() {
-    override val assetFile = TestTraces.TransitionTrace.FILE
-    override val traceName = "Transitions trace"
-    override val startTimeTrace = TestTraces.TransitionTrace.START_TIME
-    override val endTimeTrace = TestTraces.TransitionTrace.END_TIME
-    override val validSliceTime = TestTraces.TransitionTrace.VALID_SLICE_TIME
-    override val invalidSliceTime = TestTraces.TransitionTrace.INVALID_SLICE_TIME
-    override val traceType = TraceType.TRANSITION
-    override val invalidSizeMessage = "Transitions trace cannot be empty"
-    override val expectedSlicedTraceSize = 1
-
-    override fun doParse(reader: ResultReader) = reader.readTransitionsTrace()
-    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
-    override fun setupWriter(writer: ResultWriter): ResultWriter {
-        return super.setupWriter(writer).also {
-            val trace = readAssetAsFile("transition_trace.winscope")
-            it.addTraceResult(TraceType.TRANSITION, trace)
-        }
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseWM.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseWM.kt
deleted file mode 100644
index 66df354..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultReaderTestParseWM.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.common.io.TraceType
-
-/** Tests for [ResultReader] parsing [TraceType.WM] */
-class ResultReaderTestParseWM : BaseResultReaderTestParseTrace() {
-    override val assetFile = TestTraces.WMTrace.FILE
-    override val traceName = "WM trace"
-    override val startTimeTrace = TestTraces.WMTrace.START_TIME
-    override val endTimeTrace = TestTraces.WMTrace.END_TIME
-    override val validSliceTime = TestTraces.WMTrace.SLICE_TIME
-    override val invalidSliceTime = startTimeTrace
-    override val traceType = TraceType.WM
-    override val expectedSlicedTraceSize: Int = 2
-
-    override fun doParse(reader: ResultReader) = reader.readWmTrace()
-    override fun getTime(traceTime: Timestamp) = traceTime.elapsedNanos
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultWriterTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultWriterTest.kt
deleted file mode 100644
index 3ee69b3..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/ResultWriterTest.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.flicker.io
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.TestTraces
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Tests for [ResultWriter] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@SuppressLint("VisibleForTests")
-class ResultWriterTest {
-    @Test
-    fun cannotWriteFileWithoutScenario() {
-        val exception =
-            assertThrows<IllegalArgumentException> {
-                val writer =
-                    newTestResultWriter().forScenario(ScenarioBuilder().createEmptyScenario())
-                writer.write()
-            }
-
-        assertExceptionMessage(exception, "Scenario shouldn't be empty")
-    }
-
-    @Test
-    fun writesEmptyFile() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        val writer = newTestResultWriter()
-        val result = writer.write()
-        val path = result.artifact
-        Truth.assertWithMessage("File exists").that(path.exists()).isTrue()
-        Truth.assertWithMessage("Transition start time")
-            .that(result.transitionTimeRange.start)
-            .isEqualTo(CrossPlatform.timestamp.min())
-        Truth.assertWithMessage("Transition end time")
-            .that(result.transitionTimeRange.end)
-            .isEqualTo(CrossPlatform.timestamp.max())
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(0)
-    }
-
-    @Test
-    fun writesUndefinedFile() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        val writer =
-            ResultWriter().forScenario(TEST_SCENARIO).withOutputDir(getDefaultFlickerOutputDir())
-        val result = writer.write()
-        val path = result.artifact
-        validateFileName(path, RunStatus.UNDEFINED)
-    }
-
-    @Test
-    fun writesRunCompleteFile() {
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        val writer = newTestResultWriter().setRunComplete()
-        val result = writer.write()
-        val path = result.artifact
-        validateFileName(path, RunStatus.RUN_EXECUTED)
-    }
-
-    @Test
-    fun writesRunFailureFile() {
-        outputFileName(RunStatus.RUN_FAILED).deleteIfExists()
-        val writer = newTestResultWriter().setRunFailed(EXPECTED_FAILURE)
-        val result = writer.write()
-        val path = result.artifact
-        validateFileName(path, RunStatus.RUN_FAILED)
-        Truth.assertWithMessage("Expected assertion")
-            .that(result.executionError)
-            .isEqualTo(EXPECTED_FAILURE)
-    }
-
-    @Test
-    fun writesTransitionTime() {
-        val writer =
-            newTestResultWriter()
-                .setTransitionStartTime(TestTraces.TIME_5)
-                .setTransitionEndTime(TestTraces.TIME_10)
-
-        val result = writer.write()
-        Truth.assertWithMessage("Transition start time")
-            .that(result.transitionTimeRange.start)
-            .isEqualTo(TestTraces.TIME_5)
-        Truth.assertWithMessage("Transition end time")
-            .that(result.transitionTimeRange.end)
-            .isEqualTo(TestTraces.TIME_10)
-    }
-
-    @Test
-    fun writeWMTrace() {
-        val writer = newTestResultWriter().addTraceResult(TraceType.WM, TestTraces.WMTrace.FILE)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.WM))
-            .isTrue()
-    }
-
-    @Test
-    fun writeLayersTrace() {
-        val writer = newTestResultWriter().addTraceResult(TraceType.SF, TestTraces.LayerTrace.FILE)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.SF))
-            .isTrue()
-    }
-
-    @Test
-    fun writeTransactionTrace() {
-        val writer =
-            newTestResultWriter()
-                .addTraceResult(TraceType.TRANSACTION, TestTraces.TransactionTrace.FILE)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.TRANSACTION))
-            .isTrue()
-    }
-
-    @Test
-    fun writeTransitionTrace() {
-        val writer =
-            newTestResultWriter()
-                .addTraceResult(TraceType.TRANSITION, TestTraces.TransitionTrace.FILE)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(1)
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.TRANSITION))
-            .isTrue()
-    }
-
-    @Test
-    fun writeAllTraces() {
-        val writer =
-            newTestResultWriter()
-                .addTraceResult(TraceType.WM, TestTraces.WMTrace.FILE)
-                .addTraceResult(TraceType.SF, TestTraces.LayerTrace.FILE)
-                .addTraceResult(TraceType.TRANSITION, TestTraces.TransactionTrace.FILE)
-                .addTraceResult(TraceType.TRANSACTION, TestTraces.TransitionTrace.FILE)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(4)
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.WM))
-            .isTrue()
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.WM))
-            .isTrue()
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.TRANSITION))
-            .isTrue()
-        Truth.assertWithMessage("Has file with type")
-            .that(reader.hasTraceFile(TraceType.TRANSACTION))
-            .isTrue()
-    }
-
-    companion object {
-        private val EXPECTED_FAILURE = IllegalArgumentException("Expected test exception")
-
-        private fun validateFileName(filePath: File, status: RunStatus) {
-            Truth.assertWithMessage("File name contains run status")
-                .that(filePath.name)
-                .contains(status.prefix)
-        }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/io/TraceTypeTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/io/TraceTypeTest.kt
deleted file mode 100644
index 6fbd9ba..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/io/TraceTypeTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.flicker.io
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Tests for [TraceType] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TraceTypeTest {
-    @Test
-    fun canParseTraceTypes() {
-        assertFileName(TraceType.SF)
-        assertFileName(TraceType.WM)
-        assertFileName(TraceType.TRANSACTION)
-        assertFileName(TraceType.TRANSITION)
-        assertFileName(TraceType.SCREEN_RECORDING)
-    }
-
-    @Test
-    fun canParseDumpTypes() {
-        assertFileName(TraceType.SF_DUMP)
-        assertFileName(TraceType.WM_DUMP)
-        assertFileName(
-            TraceType.SF_DUMP,
-            TraceType.fromFileName("prefix${TraceType.SF_DUMP.fileName}")
-        )
-        assertFileName(
-            TraceType.WM_DUMP,
-            TraceType.fromFileName("prefix${TraceType.WM_DUMP.fileName}")
-        )
-    }
-
-    @Test
-    fun failParseInvalidTypes() {
-        assertFailure("prefix${TraceType.SF.fileName}")
-        assertFailure("prefix${TraceType.WM.fileName}")
-        assertFailure("prefix${TraceType.TRANSACTION.fileName}")
-        assertFailure("prefix${TraceType.TRANSITION.fileName}")
-        assertFailure("prefix${TraceType.SCREEN_RECORDING.fileName}")
-        assertFailure("${TraceType.SF_DUMP.fileName}suffix")
-        assertFailure("${TraceType.WM_DUMP.fileName}suffix")
-    }
-
-    private fun assertFailure(fileName: String) {
-        assertThrows<IllegalStateException> { TraceType.fromFileName(fileName) }
-    }
-
-    private fun assertFileName(
-        type: TraceType,
-        newInstance: TraceType = TraceType.fromFileName(type.fileName)
-    ) {
-        Truth.assertWithMessage("Trace type matches file name").that(newInstance).isEqualTo(type)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt
deleted file mode 100644
index f251557..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerBlockJUnit4ClassRunnerTest.kt
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.annotation.SuppressLint
-import android.app.Instrumentation
-import androidx.test.filters.FlakyTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.getScenarioTraces
-import com.android.server.wm.flicker.helpers.BrowserAppHelper
-import com.android.server.wm.flicker.helpers.IS_FAAS_ENABLED
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.google.common.truth.Truth
-import org.junit.Assume
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.Description
-import org.junit.runner.RunWith
-import org.junit.runner.manipulation.Filter
-import org.junit.runner.notification.RunNotifier
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-import org.junit.runners.model.TestClass
-import org.junit.runners.parameterized.TestWithParameters
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.Mockito.atLeast
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-/**
- * Contains [FlickerBlockJUnit4ClassRunner] tests.
- *
- * To run this test: `atest FlickerLibTest:FlickerBlockJUnit4ClassRunnerTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@SuppressLint("VisibleForTests")
-class FlickerBlockJUnit4ClassRunnerTest {
-    @Test
-    fun doesNotRunWithEmptyTestParameter() {
-        val testClass = TestClass(SimpleFaasTest::class.java)
-        val test = TestWithParameters("[PARAMS]", testClass, listOf())
-        try {
-            val runner = createRunner(test)
-            runner.run(RunNotifier())
-            error("Expected runner to fail but did not")
-        } catch (e: Throwable) {
-            Truth.assertWithMessage("Expected failure")
-                .that(e)
-                .hasMessageThat()
-                .contains(NO_SCENARIO_MESSAGE)
-        }
-    }
-
-    @Test
-    fun doesNotRunWithoutValidFlickerTest() {
-        val testClass = TestClass(SimpleFaasTest::class.java)
-        val test = TestWithParameters("[PARAMS]", testClass, listOf("invalid param"))
-        try {
-            val runner = createRunner(test)
-            runner.run(RunNotifier())
-            error("Expected runner to fail but did not")
-        } catch (e: Throwable) {
-            Truth.assertWithMessage("Expected failure")
-                .that(e)
-                .hasMessageThat()
-                .contains(NO_SCENARIO_MESSAGE)
-        }
-    }
-
-    @Test
-    fun runsWithValidFlickerTest() {
-        val testClass = TestClass(SimpleFaasTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-        val runner = createRunner(test)
-        runner.run(RunNotifier())
-    }
-
-    @Test
-    fun flakyTestsRunWithNoFilter() {
-        val testClass = TestClass(SimpleTestWithFlakyTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-        val runner = createRunner(test)
-        flakyTestRuns = 0
-        runner.run(RunNotifier())
-        Truth.assertThat(runner.testCount()).isEqualTo(2)
-        Truth.assertThat(flakyTestRuns).isEqualTo(1)
-    }
-
-    @Test
-    fun canFilterOutFlakyTests() {
-        val testClass = TestClass(SimpleTestWithFlakyTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-        val runner = createRunner(test)
-        runner.filter(FLAKY_TEST_FILTER)
-        flakyTestRuns = 0
-        val notifier = mock(RunNotifier::class.java)
-        runner.run(notifier)
-        Truth.assertThat(runner.testCount()).isEqualTo(1)
-        Truth.assertThat(flakyTestRuns).isEqualTo(0)
-        verify(notifier, never())
-            .fireTestStarted(
-                argThat { description -> description.methodName.contains("flakyTest") }
-            )
-    }
-
-    @FlakyTest
-    @Test
-    fun injectsFlickerServiceTests() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        Assume.assumeTrue(IS_FAAS_ENABLED)
-
-        val testClass = TestClass(SimpleFaasTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-        val runner = createRunner(test)
-        val notifier = mock(RunNotifier::class.java)
-        runner.run(notifier)
-        Truth.assertThat(runner.testCount()).isAtLeast(2)
-        verify(notifier).fireTestStarted(argThat { it.methodName.contains("test") })
-        verify(notifier).fireTestFinished(argThat { it.methodName.contains("test") })
-        verify(notifier, atLeast(1)).fireTestStarted(argThat { it.methodName.contains("FaaS") })
-        verify(notifier, atLeast(1)).fireTestFinished(argThat { it.methodName.contains("FaaS") })
-    }
-
-    /*@Test
-    fun injectedFlickerTestsAreNotExcludedByFilter() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        Assume.assumeTrue(IS_FAAS_ENABLED)
-
-        val testClass = TestClass(SimpleFaasTestWithFlakyTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-        val runner = FlickerBlockJUnit4ClassRunner(test)
-        runner.filter(FLAKY_TEST_FILTER)
-        val notifier = mock(RunNotifier::class.java)
-        runner.run(notifier)
-        val executionErrors = runner.executionErrors()
-        if (executionErrors.isNotEmpty()) {
-            throw executionErrors.first()
-        }
-        Truth.assertThat(runner.testCount()).isAtLeast(2)
-        verify(notifier).fireTestStarted(argThat { it.methodName.contains("test") })
-        verify(notifier).fireTestFinished(argThat { it.methodName.contains("test") })
-        verify(notifier, atLeast(1)).fireTestStarted(argThat { it.methodName.contains("FaaS") })
-        verify(notifier, atLeast(1)).fireTestFinished(argThat { it.methodName.contains("FaaS") })
-        verify(notifier, never())
-            .fireTestStarted(
-                argThat { description -> description.methodName.contains("flakyTest") }
-            )
-    }
-
-    @Test
-    fun transitionNotRerunWithFaasEnabled() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        Assume.assumeTrue(IS_FAAS_ENABLED)
-
-        transitionRunCount = 0
-        val testClass = TestClass(TransitionRunCounterWithFaasTest::class.java)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(parameters[0]))
-
-        val runner = FlickerBlockJUnit4ClassRunner(test)
-        runner.run(RunNotifier())
-        Truth.assertThat(parameters[0].flicker.faasEnabled).isTrue()
-        val executionError = parameters[0].flicker.result!!.transitionExecutionError
-        Truth.assertWithMessage(
-                "No flicker execution errors were expected but got some ::$executionError"
-            )
-            .that(executionError)
-            .isNull()
-
-        Assert.assertEquals(1, transitionRunCount)
-        transitionRunCount = 0
-    }*/
-
-    @Test
-    fun reportsExecutionErrors() {
-        checkTestRunReportsExecutionErrors(AlwaysFailExecutionTestClass::class.java)
-    }
-
-    private fun checkTestRunReportsExecutionErrors(klass: Class<*>) {
-        val testClass = TestClass(klass)
-        val parameters = FlickerTestFactory.nonRotationTests()
-        val flickerTest = parameters.first()
-        val test = TestWithParameters("[PARAMS]", testClass, listOf(flickerTest))
-
-        val runner = createRunner(test)
-        val notifier = mock(RunNotifier::class.java)
-
-        runner.run(notifier)
-        verify(notifier)
-            .fireTestFailure(
-                argThat { failure ->
-                    failure.message.contains(TRANSITION_FAILURE_MESSAGE) &&
-                        failure.description.isTest &&
-                        failure.description.displayName ==
-                            "test[${flickerTest.scenario.description}](${klass.name})"
-                }
-            )
-    }
-
-    /** Below are all the mock test classes uses for testing purposes */
-    @RunWith(Parameterized::class)
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    open class SimpleTest(protected val flicker: FlickerTest) {
-        val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-        private val testApp: BrowserAppHelper = BrowserAppHelper(instrumentation)
-
-        @FlickerBuilderProvider
-        open fun buildFlicker(): FlickerBuilder {
-            return FlickerBuilder(instrumentation).usingExistingTraces {
-                getScenarioTraces("AppLaunch")
-            }
-        }
-
-        @Test
-        fun test() {
-            flicker.assertWm {
-                // Random test to make sure flicker transition is executed
-                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
-            }
-        }
-    }
-
-    @RunWith(Parameterized::class)
-    @FlickerServiceCompatible
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    open class SimpleFaasTest(flicker: FlickerTest) : SimpleTest(flicker)
-
-    @RunWith(Parameterized::class)
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    class AlwaysFailExecutionTestClass(private val flicker: FlickerTest) {
-        val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-
-        @FlickerBuilderProvider
-        fun buildFlicker(): FlickerBuilder {
-            return FlickerBuilder(instrumentation).apply {
-                withoutScreenRecorder()
-                transitions { error(TRANSITION_FAILURE_MESSAGE) }
-            }
-        }
-
-        @Test
-        fun test() {
-            flicker.assertWm {
-                // Random test to make sure flicker transition is executed
-                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
-            }
-        }
-    }
-
-    @RunWith(Parameterized::class)
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    open class SimpleTestWithFlakyTest(flicker: FlickerTest) : SimpleTest(flicker) {
-        @FlakyTest
-        @Test
-        fun flakyTest() {
-            flakyTestRuns++
-            flicker.assertWm {
-                // Random test to make sure flicker transition is executed
-                this.visibleWindowsShownMoreThanOneConsecutiveEntry()
-            }
-        }
-    }
-
-    @RunWith(Parameterized::class)
-    @FlickerServiceCompatible
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    class SimpleFaasTestWithFlakyTest(flicker: FlickerTest) : SimpleTestWithFlakyTest(flicker)
-
-    @RunWith(Parameterized::class)
-    @FlickerServiceCompatible
-    @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-    class TransitionRunCounterWithFaasTest(flicker: FlickerTest) : SimpleFaasTest(flicker) {
-        @FlickerBuilderProvider
-        override fun buildFlicker(): FlickerBuilder {
-            return FlickerBuilder(instrumentation).apply { transitions { transitionRunCount++ } }
-        }
-    }
-
-    companion object {
-        const val TRANSITION_FAILURE_MESSAGE = "Transition execution failed"
-        private val NO_SCENARIO_MESSAGE = "Unable to extract ${FlickerTest::class.simpleName}"
-
-        val FLAKY_TEST_FILTER =
-            object : Filter() {
-                override fun shouldRun(description: Description): Boolean {
-                    val hasFlakyAnnotation =
-                        description.annotations.filterIsInstance<FlakyTest>().isNotEmpty()
-                    if (hasFlakyAnnotation && description.isTest) {
-                        return false // filter out
-                    }
-                    return true
-                }
-
-                override fun describe(): String {
-                    return "no flaky tests"
-                }
-            }
-
-        var transitionRunCount = 0
-        var flakyTestRuns = 0
-
-        private fun createRunner(baseTest: TestWithParameters) =
-            FlickerParametersRunnerFactory().createRunnerForTestWithParameters(baseTest)
-                as FlickerBlockJUnit4ClassRunner
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerServiceDecoratorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerServiceDecoratorTest.kt
deleted file mode 100644
index b0d72ed..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/FlickerServiceDecoratorTest.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.google.common.truth.Truth
-import kotlin.reflect.KClass
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.runners.model.TestClass
-import org.junit.runners.parameterized.TestWithParameters
-
-/** Tests for [FlickerServiceDecorator] */
-@SuppressLint("VisibleForTests")
-class FlickerServiceDecoratorTest {
-    @Before
-    fun setup() {
-        DataStore.clear()
-    }
-
-    @Test
-    fun passValidClass() {
-        val test =
-            TestWithParameters(
-                "test",
-                TestClass(TestUtils.DummyTestClassValid::class.java),
-                listOf(TestUtils.VALID_ARGS_EMPTY)
-            )
-        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
-        var failures = decorator.doValidateConstructor()
-        Truth.assertWithMessage("Failure count").that(failures).isEmpty()
-
-        failures = decorator.doValidateInstanceMethods()
-        Truth.assertWithMessage("Failure count").that(failures).isEmpty()
-    }
-
-    @Test
-    fun hasUniqueMethodNames() {
-        val test =
-            TestWithParameters(
-                "test",
-                TestClass(FlickerBlockJUnit4ClassRunnerTest.SimpleFaasTest::class.java),
-                listOf(TestUtils.VALID_ARGS_EMPTY)
-            )
-        val decorator = FlickerServiceDecorator(test.testClass, TEST_SCENARIO, inner = null)
-        val methods =
-            decorator.getTestMethods(
-                FlickerBlockJUnit4ClassRunnerTest.SimpleFaasTest(FlickerTest())
-            )
-        val duplicatedMethods = methods.groupBy { it.name }.filter { it.value.size > 1 }
-
-        if (isShellTransitionsEnabled) {
-            Truth.assertWithMessage("Methods").that(methods).isNotEmpty()
-        }
-        Truth.assertWithMessage("Unique methods").that(duplicatedMethods).isEmpty()
-    }
-
-    @Test
-    fun failNoProviderMethods() {
-        assertFailProviderMethod(
-            TestUtils.DummyTestClassEmpty::class,
-            expectedExceptions =
-                listOf("One object should be annotated with @FlickerBuilderProvider")
-        )
-    }
-
-    @Test
-    fun failMultipleProviderMethods() {
-        assertFailProviderMethod(
-            TestUtils.DummyTestClassMultipleProvider::class,
-            expectedExceptions =
-                listOf("Only one object should be annotated with @FlickerBuilderProvider")
-        )
-    }
-
-    @Test
-    fun failStaticProviderMethod() {
-        assertFailProviderMethod(
-            TestUtils.DummyTestClassProviderStatic::class,
-            expectedExceptions = listOf("Method myMethod() should not be static")
-        )
-    }
-
-    @Test
-    fun failPrivateProviderMethod() {
-        assertFailProviderMethod(
-            TestUtils.DummyTestClassProviderPrivateVoid::class,
-            expectedExceptions =
-                listOf(
-                    "Method myMethod() should be public",
-                    "Method myMethod() should return a " +
-                        "${FlickerBuilder::class.java.simpleName} object"
-                )
-        )
-    }
-
-    @Test
-    fun failConstructorWithNoArguments() {
-        assertFailConstructor(emptyList())
-    }
-
-    @Test
-    fun failWithInvalidConstructorArgument() {
-        assertFailConstructor(listOf(1, 2, 3))
-    }
-
-    private fun assertFailProviderMethod(cls: KClass<*>, expectedExceptions: List<String>) {
-        val test =
-            TestWithParameters("test", TestClass(cls.java), listOf(TestUtils.VALID_ARGS_EMPTY))
-        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
-        val failures = decorator.doValidateInstanceMethods()
-        Truth.assertWithMessage("Failure count").that(failures).hasSize(expectedExceptions.count())
-        expectedExceptions.forEachIndexed { idx, expectedException ->
-            val failure = failures[idx]
-            Truth.assertWithMessage("Failure")
-                .that(failure)
-                .hasMessageThat()
-                .contains(expectedException)
-        }
-    }
-
-    private fun assertFailConstructor(args: List<Any>) {
-        val test =
-            TestWithParameters("test", TestClass(TestUtils.DummyTestClassEmpty::class.java), args)
-        val decorator = FlickerServiceDecorator(test.testClass, scenario = null, inner = null)
-        val failures = decorator.doValidateConstructor()
-
-        Truth.assertWithMessage("Failure count").that(failures).hasSize(1)
-
-        val failure = failures.first()
-        Truth.assertWithMessage("Expected failure")
-            .that(failure)
-            .hasMessageThat()
-            .contains(
-                "Constructor should have a parameter of type ${FlickerTest::class.simpleName}"
-            )
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/LegacyFlickerDecoratorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/junit/LegacyFlickerDecoratorTest.kt
deleted file mode 100644
index a52c8f2..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/LegacyFlickerDecoratorTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.annotation.SuppressLint
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.datastore.DataStore
-import com.android.server.wm.traces.common.ScenarioBuilder
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.TestClass
-import org.junit.runners.parameterized.TestWithParameters
-
-/** Tests for [LegacyFlickerDecorator] */
-@SuppressLint("VisibleForTests")
-class LegacyFlickerDecoratorTest {
-    @Before
-    fun setup() {
-        DataStore.clear()
-    }
-
-    @Test
-    fun hasNoTestMethods() {
-        val scenario =
-            ScenarioBuilder().forClass(TestUtils.DummyTestClassValid::class.java.name).build()
-        val test =
-            TestWithParameters(
-                "test",
-                TestClass(TestUtils.DummyTestClassValid::class.java),
-                listOf(TestUtils.VALID_ARGS_EMPTY)
-            )
-        val helper = LegacyFlickerDecorator(test.testClass, scenario, inner = null)
-        Truth.assertWithMessage("Test method count").that(helper.getTestMethods(Any())).isEmpty()
-    }
-
-    @Test
-    fun runTransitionAndAddToDatastore() {
-        val scenario =
-            ScenarioBuilder().forClass(TestUtils.DummyTestClassValid::class.java.name).build()
-        val test =
-            TestWithParameters(
-                "test",
-                TestClass(TestUtils.DummyTestClassValid::class.java),
-                listOf(TestUtils.VALID_ARGS_EMPTY)
-            )
-        val helper = LegacyFlickerDecorator(test.testClass, scenario, inner = null)
-        TestUtils.executionCount = 0
-        val method =
-            FrameworkMethod(TestUtils.DummyTestClassValid::class.java.getMethod("dummyExecute"))
-        repeat(3) {
-            helper
-                .getMethodInvoker(
-                    method,
-                    test = TestUtils.DummyTestClassValid(FlickerTest()),
-                )
-                .evaluate()
-        }
-
-        Truth.assertWithMessage("Executed").that(TestUtils.executionCount).isEqualTo(1)
-        Truth.assertWithMessage("In Datastore")
-            .that(DataStore.containsResult(TestUtils.DummyTestClassValid.SCENARIO))
-            .isTrue()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/TestUtils.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/junit/TestUtils.kt
deleted file mode 100644
index d9fcf98..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/junit/TestUtils.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.flicker.junit
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.traces.common.ScenarioBuilder
-
-object TestUtils {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    val VALID_ARGS_EMPTY = FlickerTest()
-
-    var executionCount = 0
-
-    class DummyTestClassValid(test: FlickerTest) {
-        @FlickerBuilderProvider
-        fun myMethod(): FlickerBuilder =
-            FlickerBuilder(instrumentation).apply { transitions { executionCount++ } }
-
-        fun dummyExecute() {}
-
-        companion object {
-            val SCENARIO = ScenarioBuilder().forClass(DummyTestClassValid::class.java.name).build()
-        }
-    }
-
-    class DummyTestClassEmpty
-
-    class DummyTestClassMultipleProvider {
-        @FlickerBuilderProvider fun myMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
-
-        @FlickerBuilderProvider
-        fun mySecondMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
-    }
-
-    class DummyTestClassProviderPrivateVoid {
-        @FlickerBuilderProvider private fun myMethod() {}
-    }
-
-    class DummyTestClassProviderStatic {
-        companion object {
-            @FlickerBuilderProvider
-            @JvmStatic
-            fun myMethod(): FlickerBuilder = FlickerBuilder(instrumentation)
-        }
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt
deleted file mode 100644
index fd7cd25..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerSubjectTest.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.readLayerTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.subjects.layers.LayerSubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-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 [LayerSubject] tests. To run this test: `atest FlickerLibTest:LayerSubjectTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayerSubjectTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfoImaginary() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val foundLayer = LayersTraceSubject(layersTraceEntries).first().layer("ImaginaryLayer", 0)
-        Truth.assertWithMessage("ImaginaryLayer is not found").that(foundLayer).isNull()
-    }
-
-    @Test
-    fun canTestAssertionsOnLayer() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        LayersTraceSubject(layersTraceEntries)
-            .layer("SoundVizWallpaperV2", 26033)
-            .hasBufferSize(Size.from(1440, 2960))
-            .hasScalingMode(0)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTest.kt
deleted file mode 100644
index d6cf9c0..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTest.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-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.Rect
-import com.android.server.wm.traces.common.RectF
-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.Transform
-import com.android.server.wm.traces.common.region.Region
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Contains [Layer] tests. To run this test: `atest FlickerLibTest:LayerTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayerTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun hasVerboseFlagsProperty() {
-        assertThat(makeLayerWithDefaults(0x0).verboseFlags).isEqualTo("")
-
-        assertThat(makeLayerWithDefaults(0x1).verboseFlags).isEqualTo("HIDDEN (0x1)")
-
-        assertThat(makeLayerWithDefaults(0x2).verboseFlags).isEqualTo("OPAQUE (0x2)")
-
-        assertThat(makeLayerWithDefaults(0x40).verboseFlags).isEqualTo("SKIP_SCREENSHOT (0x40)")
-
-        assertThat(makeLayerWithDefaults(0x80).verboseFlags).isEqualTo("SECURE (0x80)")
-
-        assertThat(makeLayerWithDefaults(0x100).verboseFlags)
-            .isEqualTo("ENABLE_BACKPRESSURE (0x100)")
-
-        assertThat(makeLayerWithDefaults(0x200).verboseFlags)
-            .isEqualTo("DISPLAY_DECORATION (0x200)")
-
-        assertThat(makeLayerWithDefaults(0x400).verboseFlags)
-            .isEqualTo("IGNORE_DESTINATION_FRAME (0x400)")
-
-        assertThat(makeLayerWithDefaults(0xc3).verboseFlags)
-            .isEqualTo("HIDDEN|OPAQUE|SKIP_SCREENSHOT|SECURE (0xc3)")
-    }
-
-    @Test
-    fun useVisibleRegionIfCompositionStateIsAvailableForVisibility() {
-        assertThat(
-                makeLayerWithDefaults(
-                        excludeCompositionState = false,
-                        visibleRegion = Region.EMPTY,
-                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
-                    )
-                    .isVisible
-            )
-            .isFalse()
-        assertThat(
-                makeLayerWithDefaults(
-                        excludeCompositionState = false,
-                        visibleRegion = Region.from(0, 0, 100, 100),
-                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
-                    )
-                    .isVisible
-            )
-            .isTrue()
-    }
-
-    @Test
-    fun fallbackOnLayerBoundsIfCompositionStateIsNotAvailableForVisibility() {
-        assertThat(
-                makeLayerWithDefaults(
-                        excludeCompositionState = true,
-                        bounds = RectF.EMPTY,
-                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
-                    )
-                    .isVisible
-            )
-            .isFalse()
-        assertThat(
-                makeLayerWithDefaults(
-                        excludeCompositionState = true,
-                        bounds = RectF.from(0f, 0f, 100f, 100f),
-                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
-                    )
-                    .isVisible
-            )
-            .isTrue()
-        assertThat(
-                makeLayerWithDefaults(
-                        excludeCompositionState = true,
-                        visibleRegion = Region.from(0, 0, 100, 100),
-                        bounds = RectF.EMPTY,
-                        activeBuffer = ActiveBuffer.from(100, 100, 1, 0)
-                    )
-                    .isVisible
-            )
-            .isFalse()
-    }
-
-    private fun makeLayerWithDefaults(
-        flags: Int = 0x0,
-        excludeCompositionState: Boolean = false,
-        visibleRegion: Region = Region.EMPTY,
-        bounds: RectF = RectF.EMPTY,
-        activeBuffer: ActiveBuffer = ActiveBuffer.EMPTY
-    ): Layer {
-        return Layer.from(
-            "",
-            0,
-            0,
-            0,
-            visibleRegion,
-            activeBuffer,
-            flags,
-            bounds,
-            Color.EMPTY,
-            false,
-            -1f,
-            -1f,
-            "",
-            RectF.EMPTY,
-            Transform.EMPTY,
-            RectF.EMPTY,
-            -1,
-            -1,
-            Transform.EMPTY,
-            HwcCompositionType.INVALID,
-            RectF.EMPTY,
-            Rect.EMPTY,
-            -1,
-            null,
-            false,
-            -1,
-            -1,
-            Transform.EMPTY,
-            Color.EMPTY,
-            RectF.EMPTY,
-            Transform.EMPTY,
-            null,
-            excludeCompositionState
-        )
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntryBuilderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntryBuilderTest.kt
deleted file mode 100644
index 31204f9..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntryBuilderTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.flicker.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [LayerTraceEntryBuilder] tests. To run this test: `atest
- * FlickerLibTest:LayerTraceEntryBuilderTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayerTraceEntryBuilderTest {
-
-    @Test
-    fun createsEntryWithCorrectClockTime() {
-        val builder =
-            LayerTraceEntryBuilder()
-                .setElapsedTimestamp("100")
-                .setLayers(emptyArray())
-                .setDisplays(emptyArray())
-                .setVSyncId("123")
-                .setRealToElapsedTimeOffsetNs("500")
-        val entry = builder.build()
-        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
-        Truth.assertThat(entry.clockTimestamp).isEqualTo(600)
-
-        Truth.assertThat(entry.timestamp.elapsedNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
-    }
-
-    @Test
-    fun supportsMissingRealToElapsedTimeOffsetNs() {
-        val builder =
-            LayerTraceEntryBuilder()
-                .setElapsedTimestamp("100")
-                .setLayers(emptyArray())
-                .setDisplays(emptyArray())
-                .setVSyncId("123")
-        val entry = builder.build()
-        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
-        Truth.assertThat(entry.clockTimestamp).isEqualTo(null)
-
-        Truth.assertThat(entry.timestamp.elapsedNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt
deleted file mode 100644
index 7c1c3be..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayerTraceEntrySubjectTest.kt
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.assertFailureFact
-import com.android.server.wm.flicker.assertThatErrorContainsDebugInfo
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readLayerTraceFromFile
-import com.android.server.wm.flicker.utils.MockLayerBuilder
-import com.android.server.wm.flicker.utils.MockLayerTraceEntryBuilder
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.component.matchers.OrComponentMatcher
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.subjects.layers.LayerTraceEntrySubject
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-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 [LayerTraceEntrySubject] tests. To run this test: `atest
- * FlickerLibTest:LayerTraceEntrySubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayerTraceEntrySubjectTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(layersTraceEntries)
-                    .first()
-                    .visibleRegion(TestComponents.IMAGINARY)
-            }
-        assertThatErrorContainsDebugInfo(error)
-        Truth.assertThat(error).hasMessageThat().contains(TestComponents.IMAGINARY.className)
-        Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
-    }
-
-    @Test
-    fun testCanInspectBeginning() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        LayerTraceEntrySubject(layersTraceEntries.entries.first())
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
-            .isVisible(TestComponents.LAUNCHER)
-    }
-
-    @Test
-    fun testCanInspectEnd() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        LayerTraceEntrySubject(layersTraceEntries.entries.last())
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
-    }
-
-    // b/75276931
-    @Test
-    fun canDetectUncoveredRegion() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val expectedRegion = Region.from(0, 0, 1440, 2960)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(935346112030, byElapsedTimestamp = true)
-                    .visibleRegion()
-                    .coversAtLeast(expectedRegion)
-            }
-        assertFailureFact(error, "Region to test").contains("SkRegion((0,0,1440,2960))")
-
-        assertFailureFact(error, "Uncovered region").contains("SkRegion((0,1440,1440,2960))")
-    }
-
-    // Visible region tests
-    @Test
-    fun canTestLayerVisibleRegion_layerDoesNotExist() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(937229257165, byElapsedTimestamp = true)
-                    .visibleRegion(TestComponents.IMAGINARY)
-                    .coversExactly(expectedVisibleRegion)
-            }
-        assertFailureFact(error, "Could not find layers")
-            .contains(TestComponents.IMAGINARY.toWindowIdentifier())
-    }
-
-    @Test
-    fun canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(937126074082, byElapsedTimestamp = true)
-                    .visibleRegion(TestComponents.DOCKER_STACK_DIVIDER)
-                    .coversExactly(expectedVisibleRegion)
-            }
-        assertFailureFact(error, "Covered region").contains("SkRegion()")
-    }
-
-    @Test
-    fun canTestLayerVisibleRegion_layerIsHiddenByParent() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val expectedVisibleRegion = Region.from(0, 0, 1, 1)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(935346112030, byElapsedTimestamp = true)
-                    .visibleRegion(TestComponents.SIMPLE_APP)
-                    .coversExactly(expectedVisibleRegion)
-            }
-        assertFailureFact(error, "Covered region").contains("SkRegion()")
-    }
-
-    @Test
-    fun canTestLayerVisibleRegion_incorrectRegionSize() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val expectedVisibleRegion = Region.from(0, 0, 1440, 99)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(937126074082, byElapsedTimestamp = true)
-                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-                    .coversExactly(expectedVisibleRegion)
-            }
-        assertFailureFact(error, "Region to test").contains("SkRegion((0,0,1440,99))")
-    }
-
-    @Test
-    fun canTestLayerVisibleRegion() {
-        val trace =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        val expectedVisibleRegion = Region.from(0, 0, 1080, 145)
-        LayersTraceSubject(trace)
-            .getEntryBySystemUpTime(90480846872160, byElapsedTimestamp = true)
-            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversExactly(expectedVisibleRegion)
-    }
-
-    @Test
-    fun canTestLayerVisibleRegion_layerIsNotVisible() {
-        val trace =
-            readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb", legacyTrace = true)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .getEntryBySystemUpTime(252794268378458, byElapsedTimestamp = true)
-                    .isVisible(TestComponents.SIMPLE_APP)
-            }
-        assertFailureFact(error, "Invisibility reason", 1).contains("Bounds is 0x0")
-    }
-
-    @Test
-    fun orComponentMatcher_visibility_oneVisibleOtherInvisible() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry =
-            MockLayerTraceEntryBuilder()
-                .addDisplay(
-                    rootLayers =
-                        listOf(
-                            MockLayerBuilder(app1Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app1Name).setVisible()),
-                            MockLayerBuilder(app2Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app2Name).setInvisible()),
-                        )
-                )
-                .build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.isVisible(ComponentNameMatcher(app1Name))
-        subject.isInvisible(ComponentNameMatcher(app2Name))
-
-        subject.isInvisible(component)
-        subject.isVisible(component)
-    }
-
-    @Test
-    fun orComponentMatcher_visibility_oneVisibleOtherMissing() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry =
-            MockLayerTraceEntryBuilder()
-                .addDisplay(
-                    rootLayers =
-                        listOf(
-                            MockLayerBuilder(app1Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app1Name).setVisible())
-                        )
-                )
-                .build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.isVisible(ComponentNameMatcher(app1Name))
-        subject.notContains(ComponentNameMatcher(app2Name))
-
-        subject.isInvisible(component)
-        subject.isVisible(component)
-    }
-
-    @Test
-    fun canUseOrComponentMatcher_visibility_allVisible() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry =
-            MockLayerTraceEntryBuilder()
-                .addDisplay(
-                    rootLayers =
-                        listOf(
-                            MockLayerBuilder(app1Name)
-                                .setContainerLayer()
-                                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                                .addChild(MockLayerBuilder("$app1Name child").setVisible()),
-                            MockLayerBuilder(app2Name)
-                                .setContainerLayer()
-                                .setAbsoluteBounds(Rect.from(200, 200, 400, 400))
-                                .addChild(MockLayerBuilder("$app2Name child").setVisible()),
-                        )
-                )
-                .build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.isVisible(ComponentNameMatcher(app1Name))
-        subject.isVisible(ComponentNameMatcher(app2Name))
-
-        assertThrows<FlickerSubjectException> { subject.isInvisible(component) }
-        subject.isVisible(component)
-    }
-
-    @Test
-    fun canUseOrComponentMatcher_contains_withOneExists() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry =
-            MockLayerTraceEntryBuilder()
-                .addDisplay(
-                    rootLayers =
-                        listOf(
-                            MockLayerBuilder(app1Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app1Name))
-                        )
-                )
-                .build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.contains(ComponentNameMatcher(app1Name))
-        subject.notContains(ComponentNameMatcher(app2Name))
-
-        subject.notContains(component)
-        subject.contains(component)
-    }
-
-    @Test
-    fun canUseOrComponentMatcher_contains_withNoneExists() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry = MockLayerTraceEntryBuilder().addDisplay(rootLayers = listOf()).build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.notContains(ComponentNameMatcher(app1Name))
-        subject.notContains(ComponentNameMatcher(app2Name))
-
-        subject.notContains(component)
-        assertThrows<FlickerSubjectException> { subject.contains(component) }
-    }
-
-    @Test
-    fun canUseOrComponentMatcher_contains_withBothExists() {
-        val app1Name = "com.simple.test.app1"
-        val app2Name = "com.simple.test.app2"
-
-        val layerTraceEntry =
-            MockLayerTraceEntryBuilder()
-                .addDisplay(
-                    rootLayers =
-                        listOf(
-                            MockLayerBuilder(app1Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app1Name)),
-                            MockLayerBuilder(app2Name)
-                                .setContainerLayer()
-                                .addChild(MockLayerBuilder(app2Name)),
-                        )
-                )
-                .build()
-
-        val subject = LayerTraceEntrySubject(layerTraceEntry)
-        val component =
-            OrComponentMatcher(
-                arrayOf(ComponentNameMatcher(app1Name), ComponentNameMatcher(app2Name))
-            )
-
-        subject.contains(ComponentNameMatcher(app1Name))
-        subject.contains(ComponentNameMatcher(app2Name))
-
-        assertThrows<FlickerSubjectException> { subject.notContains(component) }
-        subject.contains(component)
-    }
-
-    @Test
-    fun detectOccludedLayerBecauseOfRoundedCorners() {
-        val trace = readLayerTraceFromFile("layers_trace_rounded_corners.winscope")
-        val entry =
-            LayersTraceSubject(trace)
-                .getEntryBySystemUpTime(6216612368228, byElapsedTimestamp = true)
-        val defaultPkg = "com.android.server.wm.flicker.testapp"
-        val simpleActivityMatcher =
-            ComponentNameMatcher(defaultPkg, "$defaultPkg.SimpleActivity#66086")
-        val imeActivityMatcher = ComponentNameMatcher(defaultPkg, "$defaultPkg.ImeActivity#66060")
-        val simpleActivitySubject =
-            entry.layer(simpleActivityMatcher) ?: error("Layer should be available")
-        val imeActivitySubject =
-            entry.layer(imeActivityMatcher) ?: error("Layer should be available")
-        val simpleActivityLayer = simpleActivitySubject.layer
-        val imeActivityLayer = imeActivitySubject.layer
-        // both layers have the same region
-        imeActivitySubject.visibleRegion.coversExactly(simpleActivitySubject.visibleRegion.region)
-        // both are visible
-        entry.isInvisible(simpleActivityMatcher)
-        entry.isVisible(imeActivityMatcher)
-        // and simple activity is partially covered by IME activity
-        Truth.assertWithMessage("IME activity has rounded corners")
-            .that(simpleActivityLayer.occludedBy)
-            .asList()
-            .contains(imeActivityLayer)
-        // because IME activity has rounded corners
-        Truth.assertWithMessage("IME activity has rounded corners")
-            .that(imeActivityLayer.cornerRadius)
-            .isGreaterThan(0)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt
deleted file mode 100644
index 9e8caca..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceEntryTest.kt
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.assertThatErrorContainsDebugInfo
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readLayerTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-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 [LayerTraceEntry] tests. To run this test: `atest FlickerLibTest:LayersTraceTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayersTraceEntryTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val error =
-            assertThrows<AssertionError> {
-                LayersTraceSubject(layersTraceEntries).first().contains(TestComponents.IMAGINARY)
-            }
-        assertThatErrorContainsDebugInfo(error)
-    }
-
-    @Test
-    fun canParseAllLayers() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        Truth.assertThat(trace.entries).isNotEmpty()
-        Truth.assertThat(trace.first().timestamp.systemUptimeNanos).isEqualTo(922839428857)
-        Truth.assertThat(trace.last().timestamp.systemUptimeNanos).isEqualTo(941432656959)
-        Truth.assertThat(trace.last().flattenedLayers).asList().hasSize(57)
-    }
-
-    @Test
-    fun canParseVisibleLayersLauncher() {
-        val trace =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        val visibleLayers =
-            trace
-                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90480846872160))
-                .visibleLayers
-        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
-        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(6)
-        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
-        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
-        Truth.assertThat(msg).contains("NavigationBar0#0")
-        Truth.assertThat(msg).contains("ImageWallpaper#0")
-        Truth.assertThat(msg).contains("StatusBar#0")
-        Truth.assertThat(msg).contains("NexusLauncherActivity#0")
-    }
-
-    @Test
-    fun canParseVisibleLayersSplitScreen() {
-        val trace =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        val visibleLayers =
-            trace
-                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90493757372977))
-                .visibleLayers
-        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
-        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(7)
-        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
-        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
-        Truth.assertThat(msg).contains("NavigationBar0#0")
-        Truth.assertThat(msg).contains("StatusBar#0")
-        Truth.assertThat(msg).contains("DockedStackDivider#0")
-        Truth.assertThat(msg).contains("ConversationListActivity#0")
-        Truth.assertThat(msg).contains("GoogleDialtactsActivity#0")
-    }
-
-    @Test
-    fun canParseVisibleLayersInTransition() {
-        val trace =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        val visibleLayers =
-            trace
-                .getEntryExactlyAt(CrossPlatform.timestamp.from(systemUptimeNanos = 90488463619533))
-                .visibleLayers
-        val msg = "Visible Layers:\n" + visibleLayers.joinToString("\n") { "\t" + it.name }
-        Truth.assertWithMessage(msg).that(visibleLayers).asList().hasSize(10)
-        Truth.assertThat(msg).contains("ScreenDecorOverlayBottom#0")
-        Truth.assertThat(msg).contains("ScreenDecorOverlay#0")
-        Truth.assertThat(msg).contains("NavigationBar0#0")
-        Truth.assertThat(msg).contains("StatusBar#0")
-        Truth.assertThat(msg).contains("DockedStackDivider#0")
-        Truth.assertThat(msg)
-            .contains("SnapshotStartingWindow for taskId=21 - " + "task-snapshot-surface#0")
-        Truth.assertThat(msg).contains("SnapshotStartingWindow for taskId=21")
-        Truth.assertThat(msg).contains("NexusLauncherActivity#0")
-        Truth.assertThat(msg).contains("ImageWallpaper#0")
-        Truth.assertThat(msg).contains("ConversationListActivity#0")
-    }
-
-    @Test
-    fun canParseLayerHierarchy() {
-        val trace = readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        Truth.assertThat(trace.entries).isNotEmpty()
-        Truth.assertThat(trace.entries.first().timestamp.systemUptimeNanos).isEqualTo(922839428857)
-        Truth.assertThat(trace.entries.last().timestamp.systemUptimeNanos).isEqualTo(941432656959)
-        Truth.assertThat(trace.entries.first().flattenedLayers).asList().hasSize(57)
-        val layers = trace.entries.first().children
-        Truth.assertThat(layers[0].children).asList().hasSize(3)
-        Truth.assertThat(layers[1].children).isEmpty()
-    }
-
-    // b/76099859
-    @Test
-    fun canDetectOrphanLayers() {
-        try {
-            readLayerTraceFromFile(
-                    "layers_trace_orphanlayers.pb",
-                    ignoreOrphanLayers = false,
-                    legacyTrace = true
-                )
-                .first()
-                .flattenedLayers
-            error("Failed to detect orphaned layers.")
-        } catch (exception: RuntimeException) {
-            Truth.assertThat(exception.message)
-                .contains(
-                    "Failed to parse layers trace. Found orphan layer with id = 49 with" +
-                        " parentId = 1006"
-                )
-        }
-    }
-
-    @Test
-    fun testCanParseNonCroppedLayerWithHWC() {
-        val layerName = "BackColorSurface#0"
-        val layersTrace =
-            readLayerTraceFromFile("layers_trace_backcolorsurface.pb", legacyTrace = true)
-        val entry =
-            layersTrace.getEntryExactlyAt(
-                CrossPlatform.timestamp.from(systemUptimeNanos = 131954021476)
-            )
-        Truth.assertWithMessage("$layerName should not be visible")
-            .that(entry.visibleLayers.map { it.name })
-            .doesNotContain(layerName)
-        val layer = entry.flattenedLayers.first { it.name == layerName }
-        Truth.assertWithMessage("$layerName should be invisible because of HWC region")
-            .that(layer.visibilityReason)
-            .asList()
-            .contains("Visible region calculated by Composition Engine is empty")
-    }
-
-    @Test
-    fun canParseTraceEmptyState() {
-        val layersTrace =
-            readLayerTraceFromFile("layers_trace_empty_state.winscope", legacyTrace = true)
-        val emptyStates = layersTrace.filter { it.flattenedLayers.isEmpty() }
-
-        Truth.assertWithMessage("Some states in the trace should be empty")
-            .that(emptyStates)
-            .isNotEmpty()
-
-        Truth.assertWithMessage("Expected state 4d4h41m14s193ms to be empty")
-            .that(emptyStates.first().timestamp.systemUptimeNanos)
-            .isEqualTo(362474193519965)
-    }
-
-    @Test
-    fun canDetectInvisibleLayerOutOfScreen() {
-        val layersTrace = readLayerTraceFromFile("layers_trace_visible_outside_bounds.winscope")
-        val subject =
-            LayersTraceSubject(layersTrace)
-                .getEntryBySystemUpTime(1253267561044, byElapsedTimestamp = true)
-        val region = subject.visibleRegion(ComponentNameMatcher.IME_SNAPSHOT)
-        region.isEmpty()
-        subject.isInvisible(ComponentNameMatcher.IME_SNAPSHOT)
-    }
-
-    @Test
-    fun canDetectInvisibleLayerOutOfScreen_ConsecutiveLayers() {
-        val layersTrace = readLayerTraceFromFile("layers_trace_visible_outside_bounds.winscope")
-        val subject = LayersTraceSubject(layersTrace)
-        subject.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    @Test
-    fun usesRealTimestampWhenAvailableAndFallsbackOnElapsedTimestamp() {
-        var entry =
-            LayerTraceEntry(
-                elapsedTimestamp = 100,
-                clockTimestamp = 600,
-                hwcBlob = "",
-                where = "",
-                displays = emptyArray(),
-                vSyncId = 123,
-                _rootLayers = emptyArray()
-            )
-        Truth.assertThat(entry.timestamp.elapsedNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
-
-        entry =
-            LayerTraceEntry(
-                elapsedTimestamp = 100,
-                clockTimestamp = null,
-                hwcBlob = "",
-                where = "",
-                displays = emptyArray(),
-                vSyncId = 123,
-                _rootLayers = emptyArray()
-            )
-        Truth.assertThat(entry.timestamp.elapsedNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().elapsedNanos)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt
deleted file mode 100644
index 9a694a3..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceSubjectTest.kt
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-import androidx.test.filters.FlakyTest
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.assertFailureFact
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readLayerTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-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 [LayersTraceSubject] tests. To run this test: `atest
- * FlickerLibTest:LayersTraceSubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayersTraceSubjectTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        val error =
-            assertThrows<AssertionError> { LayersTraceSubject(layersTraceEntries).isEmpty() }
-        Truth.assertThat(error).hasMessageThat().contains("Trace start")
-        Truth.assertThat(error).hasMessageThat().contains("Trace end")
-    }
-
-    @Test
-    fun testCanDetectEmptyRegionFromLayerTrace() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(layersTraceEntries)
-                    .visibleRegion()
-                    .coversAtLeast(DISPLAY_REGION)
-                    .forAllEntries()
-                error("Assertion should not have passed")
-            }
-        assertFailureFact(failure, "Region to test").contains(DISPLAY_REGION.toString())
-        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,1440,1440,2880))")
-    }
-
-    @Test
-    fun testCanInspectBeginning() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        LayersTraceSubject(layersTraceEntries)
-            .first()
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
-            .isVisible(TestComponents.LAUNCHER)
-    }
-
-    @Test
-    fun testCanInspectEnd() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        LayersTraceSubject(layersTraceEntries)
-            .last()
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
-    }
-
-    @Test
-    fun testAssertionsOnRange() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-
-        LayersTraceSubject(layersTraceEntries)
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isInvisible(TestComponents.DOCKER_STACK_DIVIDER)
-            .forSystemUpTimeRange(90480846872160L, 90480994138424L)
-
-        LayersTraceSubject(layersTraceEntries)
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
-            .forSystemUpTimeRange(90491795074136L, 90493757372977L)
-    }
-
-    @Test
-    fun testCanDetectChangingAssertions() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_launch_split_screen.pb", legacyTrace = true)
-        LayersTraceSubject(layersTraceEntries)
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .notContains(TestComponents.DOCKER_STACK_DIVIDER)
-            .then()
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isInvisible(TestComponents.DOCKER_STACK_DIVIDER)
-            .then()
-            .isVisible(ComponentNameMatcher.NAV_BAR)
-            .isVisible(TestComponents.DOCKER_STACK_DIVIDER)
-            .forAllEntries()
-    }
-
-    @FlakyTest
-    @Test
-    fun testCanDetectIncorrectVisibilityFromLayerTrace() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_invalid_layer_visibility.pb", legacyTrace = true)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(layersTraceEntries)
-                    .isVisible(TestComponents.SIMPLE_APP)
-                    .then()
-                    .isInvisible(TestComponents.SIMPLE_APP)
-                    .forAllEntries()
-            }
-
-        Truth.assertThat(error)
-            .hasMessageThat()
-            .contains("layers_trace_invalid_layer_visibility.pb")
-        Truth.assertThat(error).hasMessageThat().contains("2d22h13m14s303ms")
-        Truth.assertThat(error).hasMessageThat().contains("!isVisible")
-        Truth.assertThat(error)
-            .hasMessageThat()
-            .contains(
-                "com.android.server.wm.flicker.testapp/" +
-                    "com.android.server.wm.flicker.testapp.SimpleActivity#0 is visible"
-            )
-    }
-
-    @Test
-    fun testCanDetectInvalidVisibleLayerForMoreThanOneConsecutiveEntry() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_invalid_visible_layers.pb", legacyTrace = true)
-        val error =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(layersTraceEntries)
-                    .visibleLayersShownMoreThanOneConsecutiveEntry()
-                    .forAllEntries()
-                error("Assertion should not have passed")
-            }
-
-        Truth.assertThat(error).hasMessageThat().contains("2d18h35m56s397ms")
-        Truth.assertThat(error).hasMessageThat().contains("StatusBar#0")
-        Truth.assertThat(error).hasMessageThat().contains("is not visible for 2 entries")
-    }
-
-    private fun testCanDetectVisibleLayersMoreThanOneConsecutiveEntry(trace: LayersTrace) {
-        LayersTraceSubject(trace).visibleLayersShownMoreThanOneConsecutiveEntry().forAllEntries()
-    }
-
-    @Test
-    fun testCanDetectVisibleLayersMoreThanOneConsecutiveEntry() {
-        testCanDetectVisibleLayersMoreThanOneConsecutiveEntry(
-            readLayerTraceFromFile("layers_trace_snapshot_visible.pb", legacyTrace = true)
-        )
-    }
-
-    @Test
-    fun testCanIgnoreLayerEqualNameInVisibleLayersMoreThanOneConsecutiveEntry() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_invalid_visible_layers.pb", legacyTrace = true)
-        LayersTraceSubject(layersTraceEntries)
-            .visibleLayersShownMoreThanOneConsecutiveEntry(listOf(ComponentNameMatcher.STATUS_BAR))
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanIgnoreLayerShorterNameInVisibleLayersMoreThanOneConsecutiveEntry() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("one_visible_layer_launcher_trace.pb", legacyTrace = true)
-        val launcherComponent =
-            ComponentNameMatcher(
-                "com.google.android.apps.nexuslauncher",
-                "com.google.android.apps.nexuslauncher.NexusLauncherActivity#1"
-            )
-        LayersTraceSubject(layersTraceEntries)
-            .visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherComponent))
-            .forAllEntries()
-    }
-
-    private fun detectRootLayer(fileName: String, legacyTrace: Boolean = false) {
-        val layersTrace = readLayerTraceFromFile(fileName, legacyTrace = legacyTrace)
-        for (entry in layersTrace.entries) {
-            val rootLayers = entry.children
-            Truth.assertWithMessage("Does not have any root layer")
-                .that(rootLayers.size)
-                .isGreaterThan(0)
-            val firstParentId = rootLayers.first().parentId
-            Truth.assertWithMessage("Has multiple root layers")
-                .that(rootLayers.all { it.parentId == firstParentId })
-                .isTrue()
-        }
-    }
-
-    @Test
-    fun testCanDetectRootLayer() {
-        detectRootLayer("layers_trace_root.pb", legacyTrace = true)
-    }
-
-    @Test
-    fun testCanDetectRootLayerAOSP() {
-        detectRootLayer("layers_trace_root_aosp.pb", legacyTrace = true)
-    }
-
-    @Test
-    fun canTestLayerOccludedBySplashScreenLayerIsNotVisible() {
-        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
-        val entry =
-            LayersTraceSubject(trace)
-                .getEntryBySystemUpTime(1700382131522L, byElapsedTimestamp = true)
-        entry.isInvisible(TestComponents.SIMPLE_APP)
-        entry.isVisible(ComponentNameMatcher.SPLASH_SCREEN)
-    }
-
-    @Test
-    fun testCanDetectLayerExpanding() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_openchrome.pb", legacyTrace = true)
-        val animation =
-            LayersTraceSubject(layersTraceEntries).layers("animation-leash of app_transition#0")
-        // Obtain the area of each layer and checks if the next area is
-        // greater or equal to the previous one
-        val areas =
-            animation.map {
-                val region = it.layer?.visibleRegion ?: Region()
-                val area = region.width * region.height
-                area
-            }
-        val expanding = areas.zipWithNext { currentArea, nextArea -> nextArea >= currentArea }
-
-        Truth.assertWithMessage("Animation leash should be expanding")
-            .that(expanding.all { it })
-            .isTrue()
-    }
-
-    @Test
-    fun checkVisibleRegionAppMinusPipLayer() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_pip_wmshell.pb", legacyTrace = true)
-        val subject = LayersTraceSubject(layersTraceEntries).last()
-
-        try {
-            subject.visibleRegion(TestComponents.FIXED_APP).coversExactly(DISPLAY_REGION_ROTATED)
-            error(
-                "Layer is partially covered by a Pip layer and should not cover the device screen"
-            )
-        } catch (e: AssertionError) {
-            val pipRegion = subject.visibleRegion(TestComponents.PIP_APP).region
-            val expectedWithoutPip = DISPLAY_REGION_ROTATED.minus(pipRegion)
-            subject.visibleRegion(TestComponents.FIXED_APP).coversExactly(expectedWithoutPip)
-        }
-    }
-
-    @Test
-    fun checkVisibleRegionAppPlusPipLayer() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_pip_wmshell.pb", legacyTrace = true)
-        val subject = LayersTraceSubject(layersTraceEntries).last()
-        val pipRegion = subject.visibleRegion(TestComponents.PIP_APP).region
-        subject
-            .visibleRegion(TestComponents.FIXED_APP)
-            .plus(pipRegion)
-            .coversExactly(DISPLAY_REGION_ROTATED)
-    }
-
-    @Test
-    fun checkCanDetectSplashScreen() {
-        val trace = readLayerTraceFromFile("layers_trace_splashscreen.pb", legacyTrace = true)
-        val newLayer =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.SimpleActivity"
-            )
-        LayersTraceSubject(trace)
-            .isVisible(TestComponents.LAUNCHER)
-            .then()
-            .isSplashScreenVisibleFor(TestComponents.SIMPLE_APP, isOptional = false)
-            .then()
-            .isVisible(TestComponents.SIMPLE_APP)
-            .forAllEntries()
-
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .isVisible(TestComponents.LAUNCHER)
-                    .then()
-                    .isVisible(TestComponents.SIMPLE_APP)
-                    .forAllEntries()
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("Is Invisible")
-    }
-
-    @Test
-    fun checkCanDetectMissingSplashScreen() {
-        val trace = readLayerTraceFromFile("layers_trace_splashscreen.pb", legacyTrace = true)
-        val newLayer =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.SimpleActivity"
-            )
-
-        // No splashscreen because no matching activity record
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace)
-                    .first()
-                    .isSplashScreenVisibleFor(TestComponents.SIMPLE_APP)
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("Could not find Activity Record layer")
-
-        // No splashscreen for target activity record
-        failure =
-            assertThrows<FlickerSubjectException> {
-                LayersTraceSubject(trace).first().isSplashScreenVisibleFor(TestComponents.LAUNCHER)
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("No splash screen visible")
-    }
-
-    companion object {
-        private val DISPLAY_REGION = Region.from(0, 0, 1440, 2880)
-        private val DISPLAY_REGION_ROTATED = Region.from(0, 0, 2160, 1080)
-        private const val SHELL_APP_PACKAGE = "com.android.wm.shell.flicker.testapp"
-        private val FIXED_APP =
-            ComponentNameMatcher(SHELL_APP_PACKAGE, "$SHELL_APP_PACKAGE.FixedActivity")
-        private val PIP_APP =
-            ComponentNameMatcher(SHELL_APP_PACKAGE, "$SHELL_APP_PACKAGE.PipActivity")
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt
deleted file mode 100644
index bb8b797..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/layers/LayersTraceTest.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertThatErrorContainsDebugInfo
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readLayerTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.layers.LayersTrace
-import com.android.server.wm.traces.common.subjects.layers.LayersTraceSubject
-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 [LayersTrace] tests. To run this test: `atest FlickerLibTest:LayersTraceTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayersTraceTest {
-    private fun detectRootLayer(fileName: String, legacyTrace: Boolean = false) {
-        val layersTrace = readLayerTraceFromFile(fileName, legacyTrace = legacyTrace)
-        for (entry in layersTrace.entries) {
-            val rootLayers = entry.children
-            Truth.assertWithMessage("Does not have any root layer")
-                .that(rootLayers.size)
-                .isGreaterThan(0)
-            val firstParentId = rootLayers.first().parentId
-            Truth.assertWithMessage("Has multiple root layers")
-                .that(rootLayers.all { it.parentId == firstParentId })
-                .isTrue()
-        }
-    }
-
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun testCanDetectRootLayer() {
-        detectRootLayer("layers_trace_root.pb", legacyTrace = true)
-    }
-
-    @Test
-    fun testCanDetectRootLayerAOSP() {
-        detectRootLayer("layers_trace_root_aosp.pb", legacyTrace = true)
-    }
-
-    @Test
-    fun canTestLayerOccludedByAppLayerHasVisibleRegion() {
-        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
-        val entry =
-            trace.getEntryExactlyAt(
-                CrossPlatform.timestamp.from(systemUptimeNanos = 1700382131522L)
-            )
-        val component =
-            ComponentNameMatcher("", "com.android.server.wm.flicker.testapp.SimpleActivity#0")
-        val layer = entry.getLayerWithBuffer(component)
-        Truth.assertWithMessage("App should be visible")
-            .that(layer?.visibleRegion?.isEmpty)
-            .isFalse()
-        Truth.assertWithMessage("App should visible region")
-            .that(layer?.visibleRegion?.toString())
-            .contains("SkRegion((346,1583,1094,2839))")
-
-        val splashScreenComponent =
-            ComponentNameMatcher("", "Splash Screen com.android.server.wm.flicker.testapp#0")
-        val splashScreenLayer = entry.getLayerWithBuffer(splashScreenComponent)
-        Truth.assertWithMessage("Splash screen should be visible")
-            .that(splashScreenLayer?.visibleRegion?.isEmpty)
-            .isFalse()
-        Truth.assertWithMessage("Splash screen visible region")
-            .that(splashScreenLayer?.visibleRegion?.toString())
-            .contains("SkRegion((346,1583,1094,2839))")
-    }
-
-    @Test
-    fun canTestLayerOccludedByAppLayerIsOccludedBySplashScreen() {
-        val layerName = "com.android.server.wm.flicker.testapp.SimpleActivity#0"
-        val component = ComponentNameMatcher("", layerName)
-        val trace = readLayerTraceFromFile("layers_trace_occluded.pb", legacyTrace = true)
-        val entry =
-            trace.getEntryExactlyAt(
-                CrossPlatform.timestamp.from(systemUptimeNanos = 1700382131522L)
-            )
-        val layer = entry.getLayerWithBuffer(component)
-        val occludedBy = layer?.occludedBy ?: emptyArray()
-        val partiallyOccludedBy = layer?.partiallyOccludedBy ?: emptyArray()
-        Truth.assertWithMessage("Layer $layerName should be occluded").that(occludedBy).isNotEmpty()
-        Truth.assertWithMessage("Layer $layerName should not be partially occluded")
-            .that(partiallyOccludedBy)
-            .isEmpty()
-        Truth.assertWithMessage("Layer $layerName should be occluded")
-            .that(occludedBy.joinToString())
-            .contains(
-                "Splash Screen com.android.server.wm.flicker.testapp#0 buffer:w:1440, " +
-                    "h:3040, stride:1472, format:1 frame#1 visible:" +
-                    "SkRegion((346,1583,1094,2839))"
-            )
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val layersTraceEntries =
-            readLayerTraceFromFile("layers_trace_emptyregion.pb", legacyTrace = true)
-        val error =
-            assertThrows<AssertionError> { LayersTraceSubject(layersTraceEntries).isEmpty() }
-        assertThatErrorContainsDebugInfo(error, withBlameEntry = false)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/EventLogMonitorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/EventLogMonitorTest.kt
deleted file mode 100644
index fc54317..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/EventLogMonitorTest.kt
+++ /dev/null
@@ -1,402 +0,0 @@
-package com.android.server.wm.flicker.monitor
-
-import android.os.SystemClock
-import android.util.EventLog
-import com.android.internal.jank.EventLogTags
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.now
-import com.android.server.wm.traces.common.events.CujEvent
-import com.android.server.wm.traces.common.events.CujType
-import com.android.server.wm.traces.common.events.EventLog.Companion.MAGIC_NUMBER
-import com.android.server.wm.traces.common.events.FocusEvent
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Contains [EventLogMonitor] tests. To run this test: {@code atest
- * FlickerLibTest:EventLogMonitorTest}
- */
-class EventLogMonitorTest : TraceMonitorTest<EventLogMonitor>() {
-    override val traceType = TraceType.EVENT_LOG
-    override fun getMonitor(): EventLogMonitor = EventLogMonitor()
-
-    override fun assertTrace(traceData: ByteArray) {
-        Truth.assertThat(traceData.size).isAtLeast(MAGIC_NUMBER.toByteArray().size)
-        Truth.assertThat(traceData.slice(0 until MAGIC_NUMBER.toByteArray().size))
-            .isEqualTo(MAGIC_NUMBER.toByteArray().asList())
-    }
-
-    @Test
-    fun canCaptureFocusEventLogs() {
-        val monitor = EventLogMonitor()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 111 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 222 com.google.android.apps.nexuslauncher/" +
-                "com.google.android.apps.nexuslauncher.NexusLauncherActivity (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 333 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        monitor.start()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 4749f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        val writer = newTestResultWriter()
-        monitor.stop(writer)
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 2aa30cd com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace()
-        requireNotNull(eventLog) { "EventLog was null" }
-
-        assertEquals(2, eventLog.focusEvents.size)
-        assertEquals(
-            "4749f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[0].window
-        )
-        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
-        assertEquals(
-            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[1].window
-        )
-        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
-        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
-        assertEquals(eventLog.focusEvents[0].reason, "test")
-    }
-
-    @Test
-    fun onlyCapturesLastTransition() {
-        val monitor = EventLogMonitor()
-        monitor.start()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 11111 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 22222 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        monitor.stop(newTestResultWriter())
-
-        monitor.start()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 479f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        val writer = newTestResultWriter()
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace()
-        requireNotNull(eventLog) { "EventLog was null" }
-
-        assertEquals(2, eventLog.focusEvents.size)
-        assertEquals(
-            "479f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[0].window
-        )
-        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
-        assertEquals(
-            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[1].window
-        )
-        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
-        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
-    }
-
-    @Test
-    fun ignoreFocusRequestLogs() {
-        val monitor = EventLogMonitor()
-        monitor.start()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 4749f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus request 111 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        val writer = newTestResultWriter()
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace()
-        requireNotNull(eventLog) { "EventLog was null" }
-
-        assertEquals(2, eventLog.focusEvents.size)
-        assertEquals(
-            "4749f88 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[0].window
-        )
-        assertEquals(FocusEvent.Type.LOST, eventLog.focusEvents[0].type)
-        assertEquals(
-            "7c01447 com.android.phone/com.android.phone.settings.fdn.FdnSetting (server)",
-            eventLog.focusEvents[1].window
-        )
-        assertEquals(FocusEvent.Type.GAINED, eventLog.focusEvents[1].type)
-        assertTrue(eventLog.focusEvents[0].timestamp <= eventLog.focusEvents[1].timestamp)
-        assertEquals(eventLog.focusEvents[0].reason, "test")
-    }
-
-    @Test
-    fun savesEventLogsToFile() {
-        val monitor = EventLogMonitor()
-        monitor.start()
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 4749f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus request 111 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-        val writer = newTestResultWriter()
-        monitor.stop(writer)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        Truth.assertWithMessage("Trace not found")
-            .that(reader.hasTraceFile(TraceType.EVENT_LOG))
-            .isTrue()
-    }
-
-    @Test
-    fun cropsEventsFromBeforeMonitorStart() {
-        val monitor = EventLogMonitor()
-
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 4749f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-
-        monitor.start()
-
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-
-        val writer = newTestResultWriter()
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
-
-        Truth.assertThat(eventLog.focusEvents).hasLength(1)
-        Truth.assertThat(eventLog.focusEvents.first().type).isEqualTo(FocusEvent.Type.GAINED)
-    }
-
-    @Test
-    fun cropsEventsOutsideOfTransitionTimes() {
-        val monitor = EventLogMonitor()
-        val writer = newTestResultWriter()
-        monitor.start()
-
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus leaving 4749f88 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-
-        writer.setTransitionStartTime(now())
-
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-
-        writer.setTransitionEndTime(now())
-
-        EventLog.writeEvent(
-            INPUT_FOCUS_TAG /* input_focus */,
-            "Focus entering 7c01447 com.android.phone/" +
-                "com.android.phone.settings.fdn.FdnSetting (server)",
-            "reason=test"
-        )
-
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
-
-        Truth.assertThat(eventLog.focusEvents).hasLength(1)
-        Truth.assertThat(eventLog.focusEvents.first().hasFocus()).isTrue()
-    }
-
-    @Test
-    fun canCaptureCujEvents() {
-        val monitor = EventLogMonitor()
-        val writer = newTestResultWriter()
-        monitor.start()
-        EventLogTags.writeJankCujEventsBeginRequest(
-            CujType.CUJ_NOTIFICATION_APP_START.ordinal,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        EventLogTags.writeJankCujEventsEndRequest(
-            CujType.CUJ_NOTIFICATION_APP_START.ordinal,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
-
-        assertEquals(2, eventLog.cujEvents.size)
-    }
-
-    @Test
-    fun collectsCujEventData() {
-        val monitor = EventLogMonitor()
-        val writer = newTestResultWriter()
-        monitor.start()
-        EventLogTags.writeJankCujEventsBeginRequest(
-            CujType.CUJ_LAUNCHER_QUICK_SWITCH.ordinal,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        EventLogTags.writeJankCujEventsEndRequest(
-            CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL.ordinal,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        EventLogTags.writeJankCujEventsCancelRequest(
-            CujType.CUJ_LOCKSCREEN_LAUNCH_CAMERA.ordinal,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace() ?: error("EventLog should have been created")
-
-        assertEquals(3, eventLog.cujEvents.size)
-
-        Truth.assertThat(eventLog.cujEvents[0].type).isEqualTo(CujEvent.Companion.Type.START)
-        Truth.assertThat(eventLog.cujEvents[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_QUICK_SWITCH)
-
-        Truth.assertThat(eventLog.cujEvents[1].type).isEqualTo(CujEvent.Companion.Type.END)
-        Truth.assertThat(eventLog.cujEvents[1].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
-
-        Truth.assertThat(eventLog.cujEvents[2].type).isEqualTo(CujEvent.Companion.Type.CANCEL)
-        Truth.assertThat(eventLog.cujEvents[2].cuj).isEqualTo(CujType.CUJ_LOCKSCREEN_LAUNCH_CAMERA)
-    }
-
-    @Test
-    fun canParseHandleUnknownCujTypes() {
-        val unknownCujId = Int.MAX_VALUE
-        val monitor = EventLogMonitor()
-        val writer = newTestResultWriter()
-        monitor.start()
-        EventLogTags.writeJankCujEventsBeginRequest(
-            unknownCujId,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        EventLogTags.writeJankCujEventsEndRequest(
-            unknownCujId,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        EventLogTags.writeJankCujEventsCancelRequest(
-            unknownCujId,
-            SystemClock.elapsedRealtimeNanos(),
-            SystemClock.uptimeNanos()
-        )
-        monitor.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val eventLog = reader.readEventLogTrace()
-        requireNotNull(eventLog) { "EventLog should have been created" }
-
-        assertEquals(3, eventLog.cujEvents.size)
-        Truth.assertThat(eventLog.cujEvents[0].cuj).isEqualTo(CujType.UNKNOWN)
-        Truth.assertThat(eventLog.cujEvents[1].cuj).isEqualTo(CujType.UNKNOWN)
-        Truth.assertThat(eventLog.cujEvents[2].cuj).isEqualTo(CujType.UNKNOWN)
-    }
-
-    private companion object {
-        const val INPUT_FOCUS_TAG = 62001
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.kt
deleted file mode 100644
index eb2c75f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.surfaceflinger.Layerstrace
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [LayersTraceMonitor] tests. To run this test: `atest
- * FlickerLibTest:LayersTraceMonitorTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LayersTraceMonitorTest : TraceMonitorTest<LayersTraceMonitor>() {
-    override val traceType = TraceType.SF
-    override fun getMonitor() = LayersTraceMonitor()
-
-    override fun assertTrace(traceData: ByteArray) {
-        val trace = Layerstrace.LayersTraceFileProto.parseFrom(traceData)
-        Truth.assertThat(trace.magicNumber)
-            .isEqualTo(
-                Layerstrace.LayersTraceFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl
-                    32 or
-                    Layerstrace.LayersTraceFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
-            )
-    }
-
-    @Test
-    fun withSFTracing() {
-        val trace = withSFTracing {
-            device.pressHome()
-            device.pressRecentApps()
-        }
-
-        Truth.assertWithMessage("Could not obtain SF trace").that(trace.entries).isNotEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt
deleted file mode 100644
index ecd579b..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.kt
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.app.Instrumentation
-import android.media.MediaCodec
-import android.media.MediaFormat
-import android.media.MediaParser
-import android.os.SystemClock
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Contains [ScreenRecorder] tests. To run this test: `atest FlickerLibTest:ScreenRecorderTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ScreenRecorderTest {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val mScreenRecorder =
-        ScreenRecorder(instrumentation.targetContext, getDefaultFlickerOutputDir())
-
-    @Before
-    fun clearOutputDir() {
-        SystemUtil.runShellCommand("rm -rf ${getDefaultFlickerOutputDir()}")
-    }
-
-    @After
-    fun teardown() {
-        if (mScreenRecorder.isEnabled) {
-            mScreenRecorder.stop(newTestResultWriter())
-        }
-    }
-
-    @Test
-    fun videoIsRecorded() {
-        mScreenRecorder.start()
-        val device = UiDevice.getInstance(instrumentation)
-        device.wakeUp()
-        SystemClock.sleep(500)
-        device.pressHome()
-        var remainingTime = TIMEOUT
-        do {
-            remainingTime -= 100
-            SystemClock.sleep(STEP)
-        } while (!mScreenRecorder.isFrameRecorded && remainingTime > 0)
-        val writer = newTestResultWriter()
-        mScreenRecorder.stop(writer)
-        val result = writer.write()
-
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Screen recording file exists")
-            .that(reader.hasTraceFile(TraceType.SCREEN_RECORDING))
-            .isTrue()
-
-        val outputData =
-            reader.readBytes(TraceType.SCREEN_RECORDING) ?: error("Screen recording not found")
-        val (metadataTrack, videoTrack) = parseScreenRecording(outputData)
-
-        Truth.assertThat(metadataTrack.isEmpty()).isFalse()
-        Truth.assertThat(videoTrack.isEmpty()).isFalse()
-
-        val actualMagicString = metadataTrack.copyOfRange(0, WINSCOPE_MAGIC_STRING.size)
-        Truth.assertThat(actualMagicString).isEqualTo(WINSCOPE_MAGIC_STRING)
-    }
-
-    private fun parseScreenRecording(data: ByteArray): Pair<ByteArray, ByteArray> {
-        val inputReader = ScreenRecorderSeekableInputReader(data)
-        val outputConsumer = ScreenRecorderOutputConsumer()
-        val mediaParser = MediaParser.create(outputConsumer)
-
-        while (mediaParser.advance(inputReader)) {
-            // no op
-        }
-        mediaParser.release()
-
-        return Pair(outputConsumer.getMetadataTrack(), outputConsumer.getVideoTrack())
-    }
-
-    companion object {
-        private const val TIMEOUT = 10000L
-        private const val STEP = 100L
-        private val WINSCOPE_MAGIC_STRING =
-            byteArrayOf(
-                0x23,
-                0x56,
-                0x56,
-                0x31,
-                0x4e,
-                0x53,
-                0x43,
-                0x30,
-                0x50,
-                0x45,
-                0x54,
-                0x31,
-                0x4d,
-                0x45,
-                0x32,
-                0x23
-            ) // "#VV1NSC0PET1ME2#"
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-
-    internal class ScreenRecorderSeekableInputReader(private val bytes: ByteArray) :
-        MediaParser.SeekableInputReader {
-        private var position = 0L
-
-        override fun getPosition(): Long = position
-
-        override fun getLength(): Long = bytes.size.toLong() - position
-
-        override fun seekToPosition(position: Long) {
-            this.position = position
-        }
-
-        override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {
-            if (position >= bytes.size) {
-                return -1
-            }
-
-            val actualLength = kotlin.math.min(readLength.toLong(), bytes.size - position)
-            for (i in 0 until actualLength) {
-                buffer[(offset + i).toInt()] = bytes[(position + i).toInt()]
-            }
-
-            position += actualLength
-
-            return actualLength.toInt()
-        }
-    }
-
-    internal class ScreenRecorderOutputConsumer : MediaParser.OutputConsumer {
-        private var videoTrack = ArrayList<Byte>()
-        private var metadataTrack = ArrayList<Byte>()
-        private var videoTrackIndex = -1
-        private var metadataTrackIndex = -1
-        private val auxBuffer = ByteArray(4 * 1024)
-
-        fun getVideoTrack(): ByteArray {
-            return videoTrack.toByteArray()
-        }
-
-        fun getMetadataTrack(): ByteArray {
-            return metadataTrack.toByteArray()
-        }
-
-        override fun onSeekMapFound(seekMap: MediaParser.SeekMap) {
-            // do nothing
-        }
-
-        override fun onTrackCountFound(numberOfTracks: Int) {
-            Truth.assertThat(numberOfTracks).isEqualTo(2)
-        }
-
-        override fun onTrackDataFound(i: Int, trackData: MediaParser.TrackData) {
-            if (
-                videoTrackIndex == -1 &&
-                    trackData.mediaFormat.getString(MediaFormat.KEY_MIME, "").startsWith("video/")
-            ) {
-                videoTrackIndex = i
-            }
-
-            if (
-                metadataTrackIndex == -1 &&
-                    trackData.mediaFormat.getString(MediaFormat.KEY_MIME, "") ==
-                        "application/octet-stream"
-            ) {
-                metadataTrackIndex = i
-            }
-        }
-
-        override fun onSampleDataFound(trackIndex: Int, inputReader: MediaParser.InputReader) {
-            when (trackIndex) {
-                videoTrackIndex -> processSampleData(inputReader, videoTrack)
-                metadataTrackIndex -> processSampleData(inputReader, metadataTrack)
-                else -> throw RuntimeException("unexpected track index: $trackIndex")
-            }
-        }
-
-        override fun onSampleCompleted(
-            trackIndex: Int,
-            timeMicros: Long,
-            flags: Int,
-            size: Int,
-            offset: Int,
-            cryptoData: MediaCodec.CryptoInfo?
-        ) {
-            // do nothing
-        }
-
-        private fun processSampleData(
-            inputReader: MediaParser.InputReader,
-            buffer: ArrayList<Byte>
-        ) {
-            while (inputReader.length > 0) {
-                val requestLength = kotlin.math.min(inputReader.length, auxBuffer.size.toLong())
-                val actualLength = inputReader.read(auxBuffer, 0, requestLength.toInt())
-                if (actualLength == -1) {
-                    break
-                }
-
-                for (i in 0 until actualLength) {
-                    buffer.add(auxBuffer[i])
-                }
-            }
-        }
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt
deleted file mode 100644
index 0ed04d1..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/TraceMonitorTest.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import android.app.Instrumentation
-import android.support.test.uiautomator.UiDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.deleteIfExists
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.newTestResultWriter
-import com.android.server.wm.flicker.outputFileName
-import com.android.server.wm.traces.common.DeviceTraceDump
-import com.android.server.wm.traces.common.io.RunStatus
-import com.android.server.wm.traces.common.io.TraceType
-import com.android.server.wm.traces.parser.DeviceDumpParser
-import com.google.common.truth.Truth
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-abstract class TraceMonitorTest<T : TransitionMonitor> {
-    abstract fun getMonitor(): T
-    abstract fun assertTrace(traceData: ByteArray)
-    abstract val traceType: TraceType
-
-    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    protected val device: UiDevice = UiDevice.getInstance(instrumentation)
-    private val traceMonitor by lazy { getMonitor() }
-
-    @Before
-    fun before() {
-        Truth.assertWithMessage("Trace already enabled before starting test")
-            .that(traceMonitor.isEnabled)
-            .isFalse()
-    }
-
-    @After
-    fun teardown() {
-        device.pressHome()
-        if (traceMonitor.isEnabled) {
-            traceMonitor.stop(newTestResultWriter())
-        }
-        outputFileName(RunStatus.RUN_EXECUTED).deleteIfExists()
-        Truth.assertWithMessage("Failed to disable trace at end of test")
-            .that(traceMonitor.isEnabled)
-            .isFalse()
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun canStartTrace() {
-        traceMonitor.start()
-        Truth.assertThat(traceMonitor.isEnabled).isTrue()
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun canStopTrace() {
-        traceMonitor.start()
-        Truth.assertThat(traceMonitor.isEnabled).isTrue()
-        traceMonitor.stop(newTestResultWriter())
-        Truth.assertThat(traceMonitor.isEnabled).isFalse()
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun captureTrace() {
-        traceMonitor.start()
-        val writer = newTestResultWriter()
-        traceMonitor.stop(writer)
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        Truth.assertWithMessage("Trace file exists ${traceMonitor.traceType}")
-            .that(reader.hasTraceFile(traceMonitor.traceType))
-            .isTrue()
-
-        val trace =
-            reader.readBytes(traceMonitor.traceType)
-                ?: error("Missing trace file ${traceMonitor.traceType}")
-        Truth.assertWithMessage("Trace file has data").that(trace.size).isGreaterThan(0)
-        assertTrace(trace)
-    }
-
-    private fun validateTrace(dump: DeviceTraceDump) {
-        Truth.assertWithMessage("Could not obtain SF trace")
-            .that(dump.layersTrace?.entries ?: emptyArray())
-            .asList()
-            .isNotEmpty()
-        Truth.assertWithMessage("Could not obtain WM trace")
-            .that(dump.wmTrace?.entries ?: emptyArray())
-            .asList()
-            .isNotEmpty()
-    }
-
-    @Test
-    fun withTracing() {
-        val trace = withTracing {
-            device.pressHome()
-            device.pressRecentApps()
-        }
-
-        this.validateTrace(trace)
-    }
-
-    @Test
-    fun recordTraces() {
-        val trace = recordTraces {
-            device.pressHome()
-            device.pressRecentApps()
-        }
-
-        val dump = DeviceDumpParser.fromTrace(trace.first, trace.second, clearCache = true)
-        this.validateTrace(dump)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.kt
deleted file mode 100644
index a4d9631..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.flicker.monitor
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.nano.WindowManagerTraceFileProto
-import com.android.server.wm.traces.common.io.TraceType
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [WindowManagerTraceMonitor] tests. To run this test: `atest
- * FlickerLibTest:LayersTraceMonitorTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerTraceMonitorTest : TraceMonitorTest<WindowManagerTraceMonitor>() {
-    override val traceType = TraceType.WM
-    override fun getMonitor() = WindowManagerTraceMonitor()
-
-    override fun assertTrace(traceData: ByteArray) {
-        val trace = WindowManagerTraceFileProto.parseFrom(traceData)
-        Truth.assertThat(trace.magicNumber)
-            .isEqualTo(
-                WindowManagerTraceFileProto.MAGIC_NUMBER_H.toLong() shl
-                    32 or
-                    WindowManagerTraceFileProto.MAGIC_NUMBER_L.toLong()
-            )
-    }
-
-    @Test
-    fun withWMTracing() {
-        val trace = withWMTracing {
-            device.pressHome()
-            device.pressRecentApps()
-        }
-
-        Truth.assertWithMessage("Could not obtain WM trace").that(trace.entries).isNotEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionSubjectTest.kt
deleted file mode 100644
index 2aedb0e..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionSubjectTest.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.region
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.subjects.region.RegionSubject
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Contains [RegionSubject] tests. To run this test: `atest FlickerLibTest:RegionSubjectTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RegionSubjectTest {
-    private fun assertFail(expectedMessage: String, predicate: () -> Any) {
-        val error = assertThrows<FlickerSubjectException> { predicate() }
-        Truth.assertThat(error).hasMessageThat().contains(expectedMessage)
-    }
-
-    private fun expectAllFailPositionChange(expectedMessage: String, rectA: Rect, rectB: Rect) {
-        assertFail(expectedMessage) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
-        }
-        assertFail(expectedMessage) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
-        }
-        assertFail(expectedMessage) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
-        }
-        assertFail(expectedMessage) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
-        }
-    }
-
-    @Test
-    fun detectPositionChangeHigher() {
-        val rectA = Rect.from(left = 0, top = 0, right = 1, bottom = 1)
-        val rectB = Rect.from(left = 0, top = 1, right = 1, bottom = 2)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
-        }
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
-        }
-    }
-
-    @Test
-    fun detectPositionChangeLower() {
-        val rectA = Rect.from(left = 0, top = 2, right = 1, bottom = 3)
-        val rectB = Rect.from(left = 0, top = 0, right = 1, bottom = 1)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
-        }
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
-        }
-    }
-
-    @Test
-    fun detectPositionChangeEqualHigherLower() {
-        val rectA = Rect.from(left = 0, top = 1, right = 1, bottom = 0)
-        val rectB = Rect.from(left = 1, top = 1, right = 2, bottom = 0)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigherOrEqual(rectB)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLowerOrEqual(rectB)
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isHigher(rectB)
-        }
-        assertFail(RegionSubject.MSG_ERROR_TOP_POSITION) {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).isLower(rectB)
-        }
-    }
-
-    @Test
-    fun detectPositionChangeInvalid() {
-        val rectA = Rect.from(left = 0, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectC = Rect.from(left = 0, top = 1, right = 3, bottom = 1)
-        expectAllFailPositionChange(RegionSubject.MSG_ERROR_LEFT_POSITION, rectA, rectB)
-        expectAllFailPositionChange(RegionSubject.MSG_ERROR_RIGHT_POSITION, rectA, rectC)
-    }
-
-    @Test
-    fun detectCoversAtLeast() {
-        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectA)
-        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectA)
-        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtLeast(rectB)
-        }
-    }
-
-    @Test
-    fun detectCoversAtMost() {
-        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectA)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectB)
-        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
-            RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).coversAtMost(rectA)
-        }
-    }
-
-    @Test
-    fun detectCoversExactly() {
-        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversExactly(rectA)
-        assertFail("SkRegion((0,0,2,1)(0,1,1,2))") {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).coversExactly(rectB)
-        }
-    }
-
-    @Test
-    fun detectOverlaps() {
-        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
-        val rectC = Rect.from(left = 2, top = 2, right = 3, bottom = 3)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectB)
-        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectA)
-        assertFail("Overlap region: SkRegion()") {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).overlaps(rectC)
-        }
-    }
-
-    @Test
-    fun detectsNotOverlaps() {
-        val rectA = Rect.from(left = 1, top = 1, right = 2, bottom = 2)
-        val rectB = Rect.from(left = 2, top = 2, right = 3, bottom = 3)
-        val rectC = Rect.from(left = 0, top = 0, right = 2, bottom = 2)
-        RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectB)
-        RegionSubject(rectB, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectA)
-        assertFail("SkRegion((1,1,2,2))") {
-            RegionSubject(rectA, timestamp = CrossPlatform.timestamp.empty()).notOverlaps(rectC)
-        }
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionTest.kt
deleted file mode 100644
index 6950a4d..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/region/RegionTest.kt
+++ /dev/null
@@ -1,1790 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.region
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.region.Region
-import kotlin.random.Random
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotSame
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Contains [Region] tests. To run this test: `atest FlickerLibTest:RegionTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RegionTest {
-    // DIFFERENCE
-    private val DIFFERENCE_WITH1 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(4, 4),
-            intArrayOf(10, 10),
-            intArrayOf(19, 19),
-            intArrayOf(19, 0),
-            intArrayOf(10, 4),
-            intArrayOf(4, 10),
-            intArrayOf(0, 19)
-        )
-    private val DIFFERENCE_WITHOUT1 =
-        arrayOf(intArrayOf(5, 5), intArrayOf(9, 9), intArrayOf(9, 5), intArrayOf(5, 9))
-
-    private val DIFFERENCE_WITH2 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(19, 0),
-            intArrayOf(9, 9),
-            intArrayOf(19, 9),
-            intArrayOf(0, 19),
-            intArrayOf(9, 19)
-        )
-    private val DIFFERENCE_WITHOUT2 =
-        arrayOf(
-            intArrayOf(10, 10),
-            intArrayOf(19, 10),
-            intArrayOf(10, 19),
-            intArrayOf(19, 19),
-            intArrayOf(29, 10),
-            intArrayOf(29, 29),
-            intArrayOf(10, 29)
-        )
-
-    private val DIFFERENCE_WITH3 =
-        arrayOf(intArrayOf(0, 0), intArrayOf(19, 0), intArrayOf(0, 19), intArrayOf(19, 19))
-    private val DIFFERENCE_WITHOUT3 =
-        arrayOf(intArrayOf(40, 40), intArrayOf(40, 59), intArrayOf(59, 40), intArrayOf(59, 59))
-
-    // INTERSECT
-    private val INTERSECT_WITH1 =
-        arrayOf(intArrayOf(5, 5), intArrayOf(9, 9), intArrayOf(9, 5), intArrayOf(5, 9))
-    private val INTERSECT_WITHOUT1 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(4, 4),
-            intArrayOf(10, 10),
-            intArrayOf(19, 19),
-            intArrayOf(19, 0),
-            intArrayOf(10, 4),
-            intArrayOf(4, 10),
-            intArrayOf(0, 19)
-        )
-
-    private val INTERSECT_WITH2 =
-        arrayOf(intArrayOf(10, 10), intArrayOf(19, 10), intArrayOf(10, 19), intArrayOf(19, 19))
-    private val INTERSECT_WITHOUT2 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(19, 0),
-            intArrayOf(9, 9),
-            intArrayOf(19, 9),
-            intArrayOf(0, 19),
-            intArrayOf(9, 19),
-            intArrayOf(29, 10),
-            intArrayOf(29, 29),
-            intArrayOf(10, 29)
-        )
-
-    // UNION
-    private val UNION_WITH1 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(4, 4),
-            intArrayOf(6, 6),
-            intArrayOf(10, 10),
-            intArrayOf(19, 19),
-            intArrayOf(19, 0),
-            intArrayOf(10, 4),
-            intArrayOf(4, 10),
-            intArrayOf(0, 19),
-            intArrayOf(5, 5),
-            intArrayOf(9, 9),
-            intArrayOf(9, 5),
-            intArrayOf(5, 9)
-        )
-    private val UNION_WITHOUT1 = arrayOf(intArrayOf(0, 20), intArrayOf(20, 20), intArrayOf(20, 0))
-
-    private val UNION_WITH2 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(19, 0),
-            intArrayOf(9, 9),
-            intArrayOf(19, 9),
-            intArrayOf(0, 19),
-            intArrayOf(9, 19),
-            intArrayOf(21, 21),
-            intArrayOf(10, 10),
-            intArrayOf(19, 10),
-            intArrayOf(10, 19),
-            intArrayOf(19, 19),
-            intArrayOf(29, 10),
-            intArrayOf(29, 29),
-            intArrayOf(10, 29)
-        )
-    private val UNION_WITHOUT2 =
-        arrayOf(
-            intArrayOf(0, 29),
-            intArrayOf(0, 20),
-            intArrayOf(9, 29),
-            intArrayOf(9, 20),
-            intArrayOf(29, 0),
-            intArrayOf(20, 0),
-            intArrayOf(29, 9),
-            intArrayOf(20, 9)
-        )
-
-    private val UNION_WITH3 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(19, 0),
-            intArrayOf(0, 19),
-            intArrayOf(19, 19),
-            intArrayOf(40, 40),
-            intArrayOf(41, 41),
-            intArrayOf(40, 59),
-            intArrayOf(59, 40),
-            intArrayOf(59, 59)
-        )
-    private val UNION_WITHOUT3 = arrayOf(intArrayOf(20, 20), intArrayOf(39, 39))
-
-    // XOR
-    private val XOR_WITH1 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(4, 4),
-            intArrayOf(10, 10),
-            intArrayOf(19, 19),
-            intArrayOf(19, 0),
-            intArrayOf(10, 4),
-            intArrayOf(4, 10),
-            intArrayOf(0, 19)
-        )
-    private val XOR_WITHOUT1 =
-        arrayOf(
-            intArrayOf(5, 5),
-            intArrayOf(6, 6),
-            intArrayOf(9, 9),
-            intArrayOf(9, 5),
-            intArrayOf(5, 9)
-        )
-
-    private val XOR_WITH2 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(19, 0),
-            intArrayOf(9, 9),
-            intArrayOf(19, 9),
-            intArrayOf(0, 19),
-            intArrayOf(9, 19),
-            intArrayOf(21, 21),
-            intArrayOf(29, 10),
-            intArrayOf(10, 29),
-            intArrayOf(20, 10),
-            intArrayOf(10, 20),
-            intArrayOf(20, 20),
-            intArrayOf(29, 29)
-        )
-    private val XOR_WITHOUT2 =
-        arrayOf(
-            intArrayOf(10, 10),
-            intArrayOf(11, 11),
-            intArrayOf(19, 10),
-            intArrayOf(10, 19),
-            intArrayOf(19, 19)
-        )
-
-    private val XOR_WITH3 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(2, 2),
-            intArrayOf(19, 0),
-            intArrayOf(0, 19),
-            intArrayOf(19, 19),
-            intArrayOf(40, 40),
-            intArrayOf(41, 41),
-            intArrayOf(40, 59),
-            intArrayOf(59, 40),
-            intArrayOf(59, 59)
-        )
-    private val XOR_WITHOUT3 = arrayOf(intArrayOf(20, 20), intArrayOf(39, 39))
-
-    // REVERSE_DIFFERENCE
-    private val REVERSE_DIFFERENCE_WITH2 =
-        arrayOf(
-            intArrayOf(29, 10),
-            intArrayOf(10, 29),
-            intArrayOf(20, 10),
-            intArrayOf(10, 20),
-            intArrayOf(20, 20),
-            intArrayOf(29, 29),
-            intArrayOf(21, 21)
-        )
-    private val REVERSE_DIFFERENCE_WITHOUT2 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(19, 0),
-            intArrayOf(0, 19),
-            intArrayOf(19, 19),
-            intArrayOf(2, 2),
-            intArrayOf(11, 11)
-        )
-
-    private val REVERSE_DIFFERENCE_WITH3 =
-        arrayOf(
-            intArrayOf(40, 40),
-            intArrayOf(40, 59),
-            intArrayOf(59, 40),
-            intArrayOf(59, 59),
-            intArrayOf(41, 41)
-        )
-    private val REVERSE_DIFFERENCE_WITHOUT3 =
-        arrayOf(
-            intArrayOf(0, 0),
-            intArrayOf(19, 0),
-            intArrayOf(0, 19),
-            intArrayOf(19, 19),
-            intArrayOf(20, 20),
-            intArrayOf(39, 39),
-            intArrayOf(2, 2)
-        )
-
-    private var mRegion: Region = Region()
-
-    private fun verifyPointsInsideRegion(area: Array<IntArray>) {
-        for (i in area.indices) {
-            assertTrue(mRegion.contains(area[i][0], area[i][1]))
-        }
-    }
-
-    private fun verifyPointsOutsideRegion(area: Array<IntArray>) {
-        for (i in area.indices) {
-            assertFalse(mRegion.contains(area[i][0], area[i][1]))
-        }
-    }
-
-    @Before
-    fun setup() {
-        mRegion = Region()
-    }
-
-    @Test
-    fun testConstructor() {
-        // Test Region()
-        Region()
-
-        // Test Region(Region)
-        val oriRegion = Region()
-        Region.from(oriRegion)
-
-        // Test Region(Rect)
-        val rect = Rect.from(0, 0, 0, 0)
-        Region.from(rect)
-
-        // Test Region(int, int, int, int)
-        Region.from(0, 0, 100, 100)
-    }
-
-    @Test
-    fun testSet1() {
-        val rect = Rect.from(1, 2, 3, 4)
-        val oriRegion = Region.from(rect)
-        assertTrue(mRegion.set(oriRegion))
-        assertEquals(1, mRegion.bounds.left)
-        assertEquals(2, mRegion.bounds.top)
-        assertEquals(3, mRegion.bounds.right)
-        assertEquals(4, mRegion.bounds.bottom)
-    }
-
-    @Test
-    fun testSet2() {
-        val rect = Rect.from(1, 2, 3, 4)
-        assertTrue(mRegion.set(rect))
-        assertEquals(1, mRegion.bounds.left)
-        assertEquals(2, mRegion.bounds.top)
-        assertEquals(3, mRegion.bounds.right)
-        assertEquals(4, mRegion.bounds.bottom)
-    }
-
-    @Test
-    fun testSet3() {
-        assertTrue(mRegion.set(1, 2, 3, 4))
-        assertEquals(1, mRegion.bounds.left)
-        assertEquals(2, mRegion.bounds.top)
-        assertEquals(3, mRegion.bounds.right)
-        assertEquals(4, mRegion.bounds.bottom)
-    }
-
-    @Test
-    fun testIsRect() {
-        assertFalse(mRegion.isRect())
-        mRegion = Region.from(1, 2, 3, 4)
-        assertTrue(mRegion.isRect())
-    }
-
-    @Test
-    fun testIsComplex() {
-        // Region is empty
-        assertFalse(mRegion.isComplex())
-
-        // Only one rectangle
-        mRegion = Region()
-        mRegion.set(1, 2, 3, 4)
-        assertFalse(mRegion.isComplex())
-
-        // More than one rectangle
-        mRegion = Region()
-        mRegion.set(1, 1, 2, 2)
-        mRegion.union(Rect.from(3, 3, 5, 5))
-        assertTrue(mRegion.isComplex())
-    }
-
-    @Test
-    fun testUnion() {
-        val rect1 = Rect.from(0, 0, 0, 0)
-        val rect2 = Rect.from(0, 0, 20, 20)
-        val rect3 = Rect.from(5, 5, 10, 10)
-        val rect4 = Rect.from(10, 10, 30, 30)
-        val rect5 = Rect.from(40, 40, 60, 60)
-
-        // union (inclusive-or) the two regions
-        mRegion.set(rect2)
-        // union null rectangle
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.union(rect1))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.union(rect3))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.union(rect4))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.union(rect5))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    @Test
-    fun testContains() {
-        mRegion.set(2, 2, 5, 5)
-        // Not contain (1, 1).
-        assertFalse(mRegion.contains(1, 1))
-
-        // Test point inside this region.
-        assertTrue(mRegion.contains(3, 3))
-
-        // Test left-top corner.
-        assertTrue(mRegion.contains(2, 2))
-
-        // Test left-bottom corner.
-        assertTrue(mRegion.contains(2, 4))
-
-        // Test right-top corner.
-        assertTrue(mRegion.contains(4, 2))
-
-        // Test right-bottom corner.
-        assertTrue(mRegion.contains(4, 4))
-
-        // Though you set 5, but 5 is not contained by this region.
-        assertFalse(mRegion.contains(5, 5))
-        assertFalse(mRegion.contains(2, 5))
-        assertFalse(mRegion.contains(5, 2))
-
-        // Set a new rectangle.
-        mRegion.set(6, 6, 8, 8)
-        assertFalse(mRegion.contains(3, 3))
-        assertTrue(mRegion.contains(7, 7))
-    }
-
-    @Test
-    fun testEmpty() {
-        assertTrue(mRegion.isEmpty)
-        mRegion = Region.from(1, 2, 3, 4)
-        assertFalse(mRegion.isEmpty)
-        mRegion.setEmpty()
-        assertTrue(mRegion.isEmpty)
-    }
-
-    @Test
-    fun testOp1() {
-        val rect1 = Rect.from(0, 0, 0, 0)
-        val rect2 = Rect.from(0, 0, 20, 20)
-        val rect3 = Rect.from(5, 5, 10, 10)
-        val rect4 = Rect.from(10, 10, 30, 30)
-        val rect5 = Rect.from(40, 40, 60, 60)
-        verifyNullRegionOp1(rect1)
-        verifyDifferenceOp1(rect1, rect2, rect3, rect4, rect5)
-        verifyIntersectOp1(rect1, rect2, rect3, rect4, rect5)
-        verifyUnionOp1(rect1, rect2, rect3, rect4, rect5)
-        verifyXorOp1(rect1, rect2, rect3, rect4, rect5)
-        verifyReverseDifferenceOp1(rect1, rect2, rect3, rect4, rect5)
-        verifyReplaceOp1(rect1, rect2, rect3, rect4, rect5)
-    }
-
-    private fun verifyNullRegionOp1(rect1: Rect) {
-        // Region without rectangle
-        mRegion = Region()
-        assertFalse(mRegion.op(rect1, Region.Op.DIFFERENCE))
-        assertFalse(mRegion.op(rect1, Region.Op.INTERSECT))
-        assertFalse(mRegion.op(rect1, Region.Op.UNION))
-        assertFalse(mRegion.op(rect1, Region.Op.XOR))
-        assertFalse(mRegion.op(rect1, Region.Op.REVERSE_DIFFERENCE))
-        assertFalse(mRegion.op(rect1, Region.Op.REPLACE))
-    }
-
-    private fun verifyDifferenceOp1(
-        rect1: Rect,
-        rect2: Rect,
-        rect3: Rect,
-        rect4: Rect,
-        rect5: Rect
-    ) {
-        // DIFFERENCE, Region with rectangle
-        // subtract the op region from the first region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(rect2)
-        assertTrue(mRegion.op(rect1, Region.Op.DIFFERENCE))
-
-        // 1. subtract rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(rect3, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH1)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
-
-        // 2. subtract rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(11, 11))
-        assertTrue(mRegion.op(rect4, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
-
-        // 3. subtract rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.op(rect5, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyIntersectOp1(
-        rect1: Rect,
-        rect2: Rect,
-        rect3: Rect,
-        rect4: Rect,
-        rect5: Rect
-    ) {
-        // INTERSECT, Region with rectangle
-        // intersect the two regions
-        mRegion = Region()
-        // intersect null rectangle
-        mRegion.set(rect2)
-        assertFalse(mRegion.op(rect1, Region.Op.INTERSECT))
-
-        // 1. intersect rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.op(rect3, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH1)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
-
-        // 2. intersect rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(9, 9))
-        assertTrue(mRegion.op(rect4, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH2)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
-
-        // 3. intersect rectangle out of this region
-        mRegion.set(rect2)
-        assertFalse(mRegion.op(rect5, Region.Op.INTERSECT))
-    }
-
-    private fun verifyUnionOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
-        // UNION, Region with rectangle
-        // union (inclusive-or) the two regions
-        mRegion = Region()
-        mRegion.set(rect2)
-        // union null rectangle
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(rect1, Region.Op.UNION))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(rect3, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(rect4, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(rect5, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    private fun verifyXorOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
-        // XOR, Region with rectangle
-        // exclusive-or the two regions
-        mRegion = Region()
-        // xor null rectangle
-        mRegion.set(rect2)
-        assertTrue(mRegion.op(rect1, Region.Op.XOR))
-
-        // 1. xor rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(rect3, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH1)
-        verifyPointsOutsideRegion(XOR_WITHOUT1)
-
-        // 2. xor rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(rect4, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH2)
-        verifyPointsOutsideRegion(XOR_WITHOUT2)
-
-        // 3. xor rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(rect5, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH3)
-        verifyPointsOutsideRegion(XOR_WITHOUT3)
-    }
-
-    private fun verifyReverseDifferenceOp1(
-        rect1: Rect,
-        rect2: Rect,
-        rect3: Rect,
-        rect4: Rect,
-        rect5: Rect
-    ) {
-        // REVERSE_DIFFERENCE, Region with rectangle
-        // reverse difference the first region from the op region
-        mRegion = Region()
-        mRegion.set(rect2)
-        // reverse difference null rectangle
-        assertFalse(mRegion.op(rect1, Region.Op.REVERSE_DIFFERENCE))
-
-        // 1. reverse difference rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertFalse(mRegion.op(rect3, Region.Op.REVERSE_DIFFERENCE))
-
-        // 2. reverse difference rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(rect4, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
-
-        // 3. reverse difference rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(rect5, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyReplaceOp1(rect1: Rect, rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
-        // REPLACE, Region with rectangle
-        // replace the dst region with the op region
-        mRegion = Region()
-        mRegion.set(rect2)
-        // subtract null rectangle
-        assertFalse(mRegion.op(rect1, Region.Op.REPLACE))
-        // subtract rectangle inside this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(rect3, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect3, mRegion.bounds)
-        // subtract rectangle overlap this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(rect4, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect4, mRegion.bounds)
-        // subtract rectangle out of this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(rect5, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect5, mRegion.bounds)
-    }
-
-    @Test
-    fun testOp2() {
-        val rect2 = Rect.from(0, 0, 20, 20)
-        val rect3 = Rect.from(5, 5, 10, 10)
-        val rect4 = Rect.from(10, 10, 30, 30)
-        val rect5 = Rect.from(40, 40, 60, 60)
-        verifyNullRegionOp2()
-        verifyDifferenceOp2(rect2)
-        verifyIntersectOp2(rect2)
-        verifyUnionOp2(rect2)
-        verifyXorOp2(rect2)
-        verifyReverseDifferenceOp2(rect2)
-        verifyReplaceOp2(rect2, rect3, rect4, rect5)
-    }
-
-    private fun verifyNullRegionOp2() {
-        // Region without rectangle
-        mRegion = Region()
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.DIFFERENCE))
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.INTERSECT))
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.UNION))
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.XOR))
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REVERSE_DIFFERENCE))
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REPLACE))
-    }
-
-    private fun verifyDifferenceOp2(rect2: Rect) {
-        // DIFFERENCE, Region with rectangle
-        // subtract the op region from the first region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(rect2)
-        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.DIFFERENCE))
-
-        // 1. subtract rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH1)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
-
-        // 2. subtract rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(11, 11))
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
-
-        // 3. subtract rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyIntersectOp2(rect2: Rect) {
-        // INTERSECT, Region with rectangle
-        // intersect the two regions
-        mRegion = Region()
-        // intersect null rectangle
-        mRegion.set(rect2)
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.INTERSECT))
-
-        // 1. intersect rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH1)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
-
-        // 2. intersect rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(9, 9))
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH2)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
-
-        // 3. intersect rectangle out of this region
-        mRegion.set(rect2)
-        assertFalse(mRegion.op(40, 40, 60, 60, Region.Op.INTERSECT))
-    }
-
-    private fun verifyUnionOp2(rect2: Rect) {
-        // UNION, Region with rectangle
-        // union (inclusive-or) the two regions
-        mRegion = Region()
-        mRegion.set(rect2)
-        // union null rectangle
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.UNION))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    private fun verifyXorOp2(rect2: Rect) {
-        // XOR, Region with rectangle
-        // exclusive-or the two regions
-        mRegion = Region()
-        mRegion.set(rect2)
-        // xor null rectangle
-        assertTrue(mRegion.op(0, 0, 0, 0, Region.Op.XOR))
-
-        // 1. xor rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH1)
-        verifyPointsOutsideRegion(XOR_WITHOUT1)
-
-        // 2. xor rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH2)
-        verifyPointsOutsideRegion(XOR_WITHOUT2)
-
-        // 3. xor rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH3)
-        verifyPointsOutsideRegion(XOR_WITHOUT3)
-    }
-
-    private fun verifyReverseDifferenceOp2(rect2: Rect) {
-        // REVERSE_DIFFERENCE, Region with rectangle
-        // reverse difference the first region from the op region
-        mRegion = Region()
-        mRegion.set(rect2)
-        // reverse difference null rectangle
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REVERSE_DIFFERENCE))
-        // reverse difference rectangle inside this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertFalse(mRegion.op(5, 5, 10, 10, Region.Op.REVERSE_DIFFERENCE))
-        // reverse difference rectangle overlap this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
-        // reverse difference rectangle out of this region
-        mRegion.set(rect2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyReplaceOp2(rect2: Rect, rect3: Rect, rect4: Rect, rect5: Rect) {
-        // REPLACE, Region w1ith rectangle
-        // replace the dst region with the op region
-        mRegion = Region()
-        mRegion.set(rect2)
-        // subtract null rectangle
-        assertFalse(mRegion.op(0, 0, 0, 0, Region.Op.REPLACE))
-        // subtract rectangle inside this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(5, 5, 10, 10, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect3, mRegion.bounds)
-        // subtract rectangle overlap this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(10, 10, 30, 30, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect4, mRegion.bounds)
-        // subtract rectangle out of this region
-        mRegion.set(rect2)
-        assertEquals(rect2, mRegion.bounds)
-        assertTrue(mRegion.op(40, 40, 60, 60, Region.Op.REPLACE))
-        assertNotSame(rect2, mRegion.bounds)
-        assertEquals(rect5, mRegion.bounds)
-    }
-
-    @Test
-    fun testOp3() {
-        val region1 = Region()
-        val region2 = Region.from(0, 0, 20, 20)
-        val region3 = Region.from(5, 5, 10, 10)
-        val region4 = Region.from(10, 10, 30, 30)
-        val region5 = Region.from(40, 40, 60, 60)
-        verifyNullRegionOp3(region1)
-        verifyDifferenceOp3(region1, region2, region3, region4, region5)
-        verifyIntersectOp3(region1, region2, region3, region4, region5)
-        verifyUnionOp3(region1, region2, region3, region4, region5)
-        verifyXorOp3(region1, region2, region3, region4, region5)
-        verifyReverseDifferenceOp3(region1, region2, region3, region4, region5)
-        verifyReplaceOp3(region1, region2, region3, region4, region5)
-    }
-
-    private fun verifyNullRegionOp3(region1: Region) {
-        // Region without rectangle
-        mRegion = Region()
-        assertFalse(mRegion.op(region1, Region.Op.DIFFERENCE))
-        assertFalse(mRegion.op(region1, Region.Op.INTERSECT))
-        assertFalse(mRegion.op(region1, Region.Op.UNION))
-        assertFalse(mRegion.op(region1, Region.Op.XOR))
-        assertFalse(mRegion.op(region1, Region.Op.REVERSE_DIFFERENCE))
-        assertFalse(mRegion.op(region1, Region.Op.REPLACE))
-    }
-
-    private fun verifyDifferenceOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // DIFFERENCE, Region with rectangle
-        // subtract the op region from the first region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(region2)
-        assertTrue(mRegion.op(region1, Region.Op.DIFFERENCE))
-
-        // 1. subtract rectangle inside this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(region3, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH1)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
-
-        // 2. subtract rectangle overlap this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(11, 11))
-        assertTrue(mRegion.op(region4, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
-
-        // 3. subtract rectangle out of this region
-        mRegion.set(region2)
-        assertTrue(mRegion.op(region5, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyIntersectOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // INTERSECT, Region with rectangle
-        // intersect the two regions
-        mRegion = Region()
-        mRegion.set(region2)
-        // intersect null rectangle
-        assertFalse(mRegion.op(region1, Region.Op.INTERSECT))
-
-        // 1. intersect rectangle inside this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.op(region3, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH1)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
-
-        // 2. intersect rectangle overlap this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(9, 9))
-        assertTrue(mRegion.op(region4, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH2)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
-
-        // 3. intersect rectangle out of this region
-        mRegion.set(region2)
-        assertFalse(mRegion.op(region5, Region.Op.INTERSECT))
-    }
-
-    private fun verifyUnionOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // UNION, Region with rectangle
-        // union (inclusive-or) the two regions
-        mRegion = Region()
-        // union null rectangle
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(region1, Region.Op.UNION))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(region3, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(region4, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(region5, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    private fun verifyXorOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // XOR, Region with rectangle
-        // exclusive-or the two regions
-        mRegion = Region()
-        // xor null rectangle
-        mRegion.set(region2)
-        assertTrue(mRegion.op(region1, Region.Op.XOR))
-
-        // 1. xor rectangle inside this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertTrue(mRegion.op(region3, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH1)
-        verifyPointsOutsideRegion(XOR_WITHOUT1)
-
-        // 2. xor rectangle overlap this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(region4, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH2)
-        verifyPointsOutsideRegion(XOR_WITHOUT2)
-
-        // 3. xor rectangle out of this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(region5, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH3)
-        verifyPointsOutsideRegion(XOR_WITHOUT3)
-    }
-
-    private fun verifyReverseDifferenceOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REVERSE_DIFFERENCE, Region with rectangle
-        // reverse difference the first region from the op region
-        mRegion = Region()
-        // reverse difference null rectangle
-        mRegion.set(region2)
-        assertFalse(mRegion.op(region1, Region.Op.REVERSE_DIFFERENCE))
-
-        // 1. reverse difference rectangle inside this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(6, 6))
-        assertFalse(mRegion.op(region3, Region.Op.REVERSE_DIFFERENCE))
-
-        // 2. reverse difference rectangle overlap this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertTrue(mRegion.contains(11, 11))
-        assertFalse(mRegion.contains(21, 21))
-        assertTrue(mRegion.op(region4, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
-
-        // 3. reverse difference rectangle out of this region
-        mRegion.set(region2)
-        assertTrue(mRegion.contains(2, 2))
-        assertFalse(mRegion.contains(41, 41))
-        assertTrue(mRegion.op(region5, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyReplaceOp3(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REPLACE, Region with rectangle
-        // replace the dst region with the op region
-        mRegion = Region()
-        mRegion.set(region2)
-        // subtract null rectangle
-        assertFalse(mRegion.op(region1, Region.Op.REPLACE))
-        // subtract rectangle inside this region
-        mRegion.set(region2)
-        assertEquals(region2.bounds, mRegion.bounds)
-        assertTrue(mRegion.op(region3, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region3.bounds, mRegion.bounds)
-        // subtract rectangle overlap this region
-        mRegion.set(region2)
-        assertEquals(region2.bounds, mRegion.bounds)
-        assertTrue(mRegion.op(region4, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region4.bounds, mRegion.bounds)
-        // subtract rectangle out of this region
-        mRegion.set(region2)
-        assertEquals(region2.bounds, mRegion.bounds)
-        assertTrue(mRegion.op(region5, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region5.bounds, mRegion.bounds)
-    }
-
-    @Test
-    fun testOp4() {
-        val rect1 = Rect.from(0, 0, 0, 0)
-        val rect2 = Rect.from(0, 0, 20, 20)
-        val region1 = Region()
-        val region2 = Region.from(0, 0, 20, 20)
-        val region3 = Region.from(5, 5, 10, 10)
-        val region4 = Region.from(10, 10, 30, 30)
-        val region5 = Region.from(40, 40, 60, 60)
-        verifyNullRegionOp4(rect1, region1)
-        verifyDifferenceOp4(rect1, rect2, region1, region3, region4, region5)
-        verifyIntersectOp4(rect1, rect2, region1, region3, region4, region5)
-        verifyUnionOp4(rect1, rect2, region1, region3, region4, region5)
-        verifyXorOp4(rect1, rect2, region1, region3, region4, region5)
-        verifyReverseDifferenceOp4(rect1, rect2, region1, region3, region4, region5)
-        verifyReplaceOp4(rect1, rect2, region1, region2, region3, region4, region5)
-    }
-
-    private fun verifyNullRegionOp4(rect1: Rect, region1: Region) {
-        // Region without rectangle
-        mRegion = Region()
-        assertFalse(mRegion.op(rect1, region1, Region.Op.DIFFERENCE))
-        assertFalse(mRegion.op(rect1, region1, Region.Op.INTERSECT))
-        assertFalse(mRegion.op(rect1, region1, Region.Op.UNION))
-        assertFalse(mRegion.op(rect1, region1, Region.Op.XOR))
-        assertFalse(mRegion.op(rect1, region1, Region.Op.REVERSE_DIFFERENCE))
-        assertFalse(mRegion.op(rect1, region1, Region.Op.REPLACE))
-    }
-
-    private fun verifyDifferenceOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // DIFFERENCE, Region with rectangle
-        // subtract the op region from the first region
-        mRegion = Region()
-        // subtract null rectangle
-        assertTrue(mRegion.op(rect2, region1, Region.Op.DIFFERENCE))
-
-        // 1. subtract rectangle inside this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region3, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH1)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
-
-        // 2. subtract rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
-
-        // 3. subtract rectangle out of this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region5, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyIntersectOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // INTERSECT, Region with rectangle
-        // intersect the two regions
-        mRegion = Region()
-        // intersect null rectangle
-        mRegion.set(rect1)
-        assertFalse(mRegion.op(rect2, region1, Region.Op.INTERSECT))
-
-        // 1. intersect rectangle inside this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region3, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH1)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
-
-        // 2. intersect rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH2)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
-
-        // 3. intersect rectangle out of this region
-        mRegion.set(rect1)
-        assertFalse(mRegion.op(rect2, region5, Region.Op.INTERSECT))
-    }
-
-    private fun verifyUnionOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // UNION, Region with rectangle
-        // union (inclusive-or) the two regions
-        mRegion = Region()
-        // union null rectangle
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region1, Region.Op.UNION))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region3, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region5, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    private fun verifyXorOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // XOR, Region with rectangle
-        // exclusive-or the two regions
-        mRegion = Region()
-        // xor null rectangle
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region1, Region.Op.XOR))
-
-        // 1. xor rectangle inside this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region3, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH1)
-        verifyPointsOutsideRegion(XOR_WITHOUT1)
-
-        // 2. xor rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH2)
-        verifyPointsOutsideRegion(XOR_WITHOUT2)
-
-        // 3. xor rectangle out of this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region5, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH3)
-        verifyPointsOutsideRegion(XOR_WITHOUT3)
-    }
-
-    private fun verifyReverseDifferenceOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REVERSE_DIFFERENCE, Region with rectangle
-        // reverse difference the first region from the op region
-        mRegion = Region()
-        // reverse difference null rectangle
-        mRegion.set(rect1)
-        assertFalse(mRegion.op(rect2, region1, Region.Op.REVERSE_DIFFERENCE))
-
-        // 1. reverse difference rectangle inside this region
-        mRegion.set(rect1)
-        assertFalse(mRegion.op(rect2, region3, Region.Op.REVERSE_DIFFERENCE))
-
-        // 2. reverse difference rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
-
-        // 3. reverse difference rectangle out of this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region5, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyReplaceOp4(
-        rect1: Rect,
-        rect2: Rect,
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REPLACE, Region with rectangle
-        // replace the dst region with the op region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(rect1)
-        assertFalse(mRegion.op(rect2, region1, Region.Op.REPLACE))
-        // subtract rectangle inside this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region3, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region3.bounds, mRegion.bounds)
-        // subtract rectangle overlap this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region4, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region4.bounds, mRegion.bounds)
-        // subtract rectangle out of this region
-        mRegion.set(rect1)
-        assertTrue(mRegion.op(rect2, region5, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region5.bounds, mRegion.bounds)
-    }
-
-    @Test
-    fun testOp5() {
-        val region1 = Region()
-        val region2 = Region.from(0, 0, 20, 20)
-        val region3 = Region.from(5, 5, 10, 10)
-        val region4 = Region.from(10, 10, 30, 30)
-        val region5 = Region.from(40, 40, 60, 60)
-        verifyNullRegionOp5(region1)
-        verifyDifferenceOp5(region1, region2, region3, region4, region5)
-        verifyIntersectOp5(region1, region2, region3, region4, region5)
-        verifyUnionOp5(region1, region2, region3, region4, region5)
-        verifyXorOp5(region1, region2, region3, region4, region5)
-        verifyReverseDifferenceOp5(region1, region2, region3, region4, region5)
-        verifyReplaceOp5(region1, region2, region3, region4, region5)
-    }
-
-    private fun verifyNullRegionOp5(region1: Region) {
-        // Region without rectangle
-        mRegion = Region()
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.DIFFERENCE))
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.INTERSECT))
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.UNION))
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.XOR))
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.REVERSE_DIFFERENCE))
-        assertFalse(mRegion.op(mRegion, region1, Region.Op.REPLACE))
-    }
-
-    private fun verifyDifferenceOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // DIFFERENCE, Region with rectangle
-        // subtract the op region from the first region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region1, Region.Op.DIFFERENCE))
-
-        // 1. subtract rectangle inside this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region3, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH1)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT1)
-
-        // 2. subtract rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT2)
-
-        // 3. subtract rectangle out of this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region5, Region.Op.DIFFERENCE))
-        verifyPointsInsideRegion(DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyIntersectOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // INTERSECT, Region with rectangle
-        // intersect the two regions
-        mRegion = Region()
-        // intersect null rectangle
-        mRegion.set(region1)
-        assertFalse(mRegion.op(region2, region1, Region.Op.INTERSECT))
-
-        // 1. intersect rectangle inside this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region3, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH1)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT1)
-
-        // 2. intersect rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.INTERSECT))
-        verifyPointsInsideRegion(INTERSECT_WITH2)
-        verifyPointsOutsideRegion(INTERSECT_WITHOUT2)
-
-        // 3. intersect rectangle out of this region
-        mRegion.set(region1)
-        assertFalse(mRegion.op(region2, region5, Region.Op.INTERSECT))
-    }
-
-    private fun verifyUnionOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // UNION, Region with rectangle
-        // union (inclusive-or) the two regions
-        mRegion = Region()
-        // union null rectangle
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region1, Region.Op.UNION))
-        assertTrue(mRegion.contains(6, 6))
-
-        // 1. union rectangle inside this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region3, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH1)
-        verifyPointsOutsideRegion(UNION_WITHOUT1)
-
-        // 2. union rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH2)
-        verifyPointsOutsideRegion(UNION_WITHOUT2)
-
-        // 3. union rectangle out of this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region5, Region.Op.UNION))
-        verifyPointsInsideRegion(UNION_WITH3)
-        verifyPointsOutsideRegion(UNION_WITHOUT3)
-    }
-
-    private fun verifyXorOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // XOR, Region with rectangle
-        // exclusive-or the two regions
-        mRegion = Region()
-        // xor null rectangle
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region1, Region.Op.XOR))
-
-        // 1. xor rectangle inside this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region3, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH1)
-        verifyPointsOutsideRegion(XOR_WITHOUT1)
-
-        // 2. xor rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH2)
-        verifyPointsOutsideRegion(XOR_WITHOUT2)
-
-        // 3. xor rectangle out of this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region5, Region.Op.XOR))
-        verifyPointsInsideRegion(XOR_WITH3)
-        verifyPointsOutsideRegion(XOR_WITHOUT3)
-    }
-
-    private fun verifyReverseDifferenceOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REVERSE_DIFFERENCE, Region with rectangle
-        // reverse difference the first region from the op region
-        mRegion = Region()
-        // reverse difference null rectangle
-        mRegion.set(region1)
-        assertFalse(mRegion.op(region2, region1, Region.Op.REVERSE_DIFFERENCE))
-
-        // 1. reverse difference rectangle inside this region
-        mRegion.set(region1)
-        assertFalse(mRegion.op(region2, region3, Region.Op.REVERSE_DIFFERENCE))
-
-        // 2. reverse difference rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH2)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT2)
-
-        // 3. reverse difference rectangle out of this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region5, Region.Op.REVERSE_DIFFERENCE))
-        verifyPointsInsideRegion(REVERSE_DIFFERENCE_WITH3)
-        verifyPointsOutsideRegion(REVERSE_DIFFERENCE_WITHOUT3)
-    }
-
-    private fun verifyReplaceOp5(
-        region1: Region,
-        region2: Region,
-        region3: Region,
-        region4: Region,
-        region5: Region
-    ) {
-        // REPLACE, Region with rectangle
-        // replace the dst region with the op region
-        mRegion = Region()
-        // subtract null rectangle
-        mRegion.set(region1)
-        assertFalse(mRegion.op(region2, region1, Region.Op.REPLACE))
-        // subtract rectangle inside this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region3, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region3.bounds, mRegion.bounds)
-        // subtract rectangle overlap this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region4, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region4.bounds, mRegion.bounds)
-        // subtract rectangle out of this region
-        mRegion.set(region1)
-        assertTrue(mRegion.op(region2, region5, Region.Op.REPLACE))
-        assertNotSame(region2.bounds, mRegion.bounds)
-        assertEquals(region5.bounds, mRegion.bounds)
-    }
-
-    val flickerRegionOperations =
-        listOf(
-            Region.Op.DIFFERENCE,
-            Region.Op.INTERSECT,
-            Region.Op.UNION,
-            Region.Op.XOR,
-            Region.Op.REVERSE_DIFFERENCE,
-            Region.Op.REPLACE
-        )
-    val nativeRegionOperations =
-        listOf(
-            android.graphics.Region.Op.DIFFERENCE,
-            android.graphics.Region.Op.INTERSECT,
-            android.graphics.Region.Op.UNION,
-            android.graphics.Region.Op.XOR,
-            android.graphics.Region.Op.REVERSE_DIFFERENCE,
-            android.graphics.Region.Op.REPLACE
-        )
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForSingleOperation() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(1)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForSingleOperationFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(1, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForTwoOperations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(2)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForTwoOperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(2, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForThreeOperations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(3)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForThreeOperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(3, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForFourOperations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(4)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForFourOperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(4, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForFiveOperations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(5)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputForFiveOperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(5, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputFor100Operations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(100, iterations = 10)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputFor100OperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(100, startEmpty = true, iterations = 10)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameOutputFor1000Operations() {
-        testFlickerRegionAndNativeRegionProvideSameOutput(100, iterations = 5)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameBoundsForSingleOperation() {
-        testFlickerRegionAndNativeRegionProvideSameBounds(1)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameBoundsForSingleOperationFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameBounds(1, startEmpty = true)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameBoundsForFiveOperations() {
-        testFlickerRegionAndNativeRegionProvideSameBounds(5)
-    }
-
-    @Test
-    fun testFlickerRegionAndNativeRegionProvideSameBoundsForFiveOperationsFromEmpty() {
-        testFlickerRegionAndNativeRegionProvideSameBounds(5, startEmpty = true)
-    }
-
-    private fun testFlickerRegionAgainstNativeRegion(
-        totalOperations: Int,
-        startEmpty: Boolean = false,
-        iterations: Int = 100,
-        assertion:
-            (
-                seed: Long,
-                history: String,
-                flickerRegion: Region,
-                nativeRegion: android.graphics.Region
-            ) -> Any,
-        seed: Long = System.currentTimeMillis()
-    ) {
-        val random = Random(seed)
-
-        for (i in 1..iterations) {
-            val flickerRegion: Region
-            val nativeRegion: android.graphics.Region
-            if (startEmpty) {
-                flickerRegion = Region.EMPTY
-                nativeRegion = android.graphics.Region()
-            } else {
-                val left = random.nextInt(0, 5000)
-                val top = random.nextInt(0, 5000)
-                val right = random.nextInt(left, 5000)
-                val bottom = random.nextInt(top, 5000)
-                flickerRegion = Region.from(left, top, right, bottom)
-                nativeRegion = android.graphics.Region(left, top, right, bottom)
-            }
-            var history = "$flickerRegion\n"
-            for (j in 1..totalOperations) {
-                val left = random.nextInt(0, 5000)
-                val top = random.nextInt(0, 5000)
-                val right = random.nextInt(left, 5000)
-                val bottom = random.nextInt(top, 5000)
-                val otherFlickerRegion = Region.from(left, top, right, bottom)
-                val otherNativeRegion = android.graphics.Region(left, top, right, bottom)
-
-                val operationIndex = random.nextInt(0, flickerRegionOperations.size)
-                val flickerOperation = flickerRegionOperations[operationIndex]
-                val nativeOperation = nativeRegionOperations[operationIndex]
-
-                history += "\t$flickerOperation $otherFlickerRegion\n"
-
-                flickerRegion.op(otherFlickerRegion, flickerOperation)
-                nativeRegion.op(otherNativeRegion, nativeOperation)
-
-                assertion(seed, history, flickerRegion, nativeRegion)
-            }
-        }
-    }
-
-    private fun testFlickerRegionAndNativeRegionProvideSameOutput(
-        totalOperations: Int,
-        startEmpty: Boolean = false,
-        iterations: Int = 100
-    ) {
-        testFlickerRegionAgainstNativeRegion(
-            totalOperations,
-            startEmpty,
-            iterations,
-            {
-                seed: Long,
-                history: String,
-                flickerRegion: Region,
-                nativeRegion: android.graphics.Region ->
-                {
-                    assertEquals(
-                        "Ran with seed $seed\n" +
-                            "$history should equal \n" +
-                            "$nativeRegion\n but was\n$flickerRegion\n\n",
-                        nativeRegion.toString(),
-                        flickerRegion.toString()
-                    )
-                }
-            }
-        )
-    }
-
-    private fun testFlickerRegionAndNativeRegionProvideSameBounds(
-        totalOperations: Int,
-        startEmpty: Boolean = false,
-        iterations: Int = 100
-    ) {
-        testFlickerRegionAgainstNativeRegion(
-            totalOperations,
-            startEmpty,
-            iterations,
-            {
-                seed: Long,
-                history: String,
-                flickerRegion: Region,
-                nativeRegion: android.graphics.Region ->
-                {
-                    val bounds = flickerRegion.bounds
-                    val nBounds = nativeRegion.bounds
-                    assertEquals(
-                        "Ran with seed $seed\n" +
-                            "$history.bounds() should equal \n" +
-                            "${nBounds}\n but was\n${bounds}\n\n",
-                        "${nBounds.left},${nBounds.top},${nBounds.right},${nBounds.bottom}",
-                        "${bounds.left},${bounds.top},${bounds.right},${bounds.bottom}"
-                    )
-                }
-            }
-        )
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/Consts.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/Consts.kt
deleted file mode 100644
index 428113c..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/Consts.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.flicker.runner
-
-import org.junit.runner.Description
-
-object Consts {
-    internal const val FAILURE = "Expected failure"
-    internal const val SETUP = "Setup"
-    internal const val TEARDOWN = "Teardown"
-    internal const val TRANSITION = "Transition"
-
-    internal fun description(obj: Any) = Description.createTestDescription(obj::class.java, "test")
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/SetupTeardownRuleTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/SetupTeardownRuleTest.kt
deleted file mode 100644
index a3caea3..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/SetupTeardownRuleTest.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.AbstractFlickerTestData
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.io.ResultWriter
-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
-import org.mockito.Mockito
-
-/** Tests for [SetupTeardownRule] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SetupTeardownRuleTest {
-    private var setupExecuted = false
-    private var teardownExecuted = false
-
-    private val runSetup: IFlickerTestData.() -> Unit = { setupExecuted = true }
-    private val runTeardown: IFlickerTestData.() -> Unit = { teardownExecuted = true }
-    private val throwError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
-
-    @Before
-    fun setup() {
-        setupExecuted = false
-        teardownExecuted = false
-    }
-
-    @Test
-    fun executesSetupTeardown() {
-        val rule = createRule(listOf(runSetup), listOf(runTeardown))
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        Truth.assertWithMessage("Setup executed").that(setupExecuted).isTrue()
-        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isTrue()
-    }
-
-    @Test
-    fun throwsSetupFailureAndExecutesTeardown() {
-        val failure =
-            assertThrows<TransitionSetupFailure> {
-                val rule = createRule(listOf(throwError, runSetup), listOf(runTeardown))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Setup executed").that(setupExecuted).isFalse()
-        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isTrue()
-    }
-
-    @Test
-    fun throwsTeardownFailure() {
-        val failure =
-            assertThrows<TransitionTeardownFailure> {
-                val rule = createRule(listOf(runSetup), listOf(throwError, runTeardown))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Setup executed").that(setupExecuted).isTrue()
-        Truth.assertWithMessage("Teardown executed").that(teardownExecuted).isFalse()
-    }
-
-    companion object {
-        private fun createRule(
-            setupCommands: List<IFlickerTestData.() -> Unit>,
-            teardownCommands: List<IFlickerTestData.() -> Unit>
-        ): SetupTeardownRule {
-            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-            val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
-            return SetupTeardownRule(
-                mockedFlicker,
-                ResultWriter(),
-                TEST_SCENARIO,
-                instrumentation,
-                setupCommands,
-                teardownCommands,
-                WindowManagerStateHelper()
-            )
-        }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TestUtils.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TestUtils.kt
deleted file mode 100644
index 1643281..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TestUtils.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.flicker.runner
-
-import com.android.server.wm.flicker.io.IResultData
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.google.common.truth.Truth
-
-object TestUtils {
-    internal fun validateTransitionTime(result: IResultData) {
-        val startTime = result.transitionTimeRange.start
-        val endTime = result.transitionTimeRange.end
-        validateTimeGreaterThan(startTime, "Start time", CrossPlatform.timestamp.min())
-        validateTimeGreaterThan(endTime, "End time", CrossPlatform.timestamp.min())
-        validateTimeGreaterThan(CrossPlatform.timestamp.max(), "End time", endTime)
-    }
-
-    internal fun validateTransitionTimeIsEmpty(result: IResultData) {
-        val startTime = result.transitionTimeRange.start
-        val endTime = result.transitionTimeRange.end
-        validateEqualTo(startTime, "Start time", CrossPlatform.timestamp.min())
-        validateEqualTo(endTime, "End time", CrossPlatform.timestamp.max())
-    }
-
-    private fun validateEqualTo(time: Timestamp, name: String, expectedValue: Timestamp) {
-        Truth.assertWithMessage("$name - systemUptimeNanos")
-            .that(time.systemUptimeNanos)
-            .isEqualTo(expectedValue.systemUptimeNanos)
-        Truth.assertWithMessage("$name - unixNanos")
-            .that(time.unixNanos)
-            .isEqualTo(expectedValue.unixNanos)
-        Truth.assertWithMessage("$name - elapsedNanos")
-            .that(time.elapsedNanos)
-            .isEqualTo(expectedValue.elapsedNanos)
-    }
-
-    private fun validateTimeGreaterThan(time: Timestamp, name: String, minValue: Timestamp) {
-        Truth.assertWithMessage("$name - systemUptimeNanos")
-            .that(time.systemUptimeNanos)
-            .isGreaterThan(minValue.systemUptimeNanos)
-        Truth.assertWithMessage("$name - unixNanos")
-            .that(time.unixNanos)
-            .isGreaterThan(minValue.unixNanos)
-        Truth.assertWithMessage("$name - elapsedNanos")
-            .that(time.elapsedNanos)
-            .isGreaterThan(minValue.elapsedNanos)
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TraceMonitorRuleTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TraceMonitorRuleTest.kt
deleted file mode 100644
index a4ac12e..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TraceMonitorRuleTest.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-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
-
-/** Tests for [TraceMonitorRule] */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TraceMonitorRuleTest {
-    private var startExecutionCount = 0
-    private var setResultExecutionCount = 0
-
-    private val monitorWithExceptionStart =
-        createMonitor({ error(Consts.FAILURE) }, { setResultExecutionCount++ })
-    private val monitorWithExceptionStop =
-        createMonitor(
-            { startExecutionCount++ },
-            { error(Consts.FAILURE) },
-        )
-    private val monitorWithoutException =
-        createMonitor({ startExecutionCount++ }, { setResultExecutionCount++ })
-
-    @Before
-    fun setup() {
-        startExecutionCount = 0
-        setResultExecutionCount = 0
-    }
-
-    @Test
-    fun executesSuccessfully() {
-        val rule = createRule(listOf(monitorWithoutException))
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(1)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
-    }
-
-    @Test
-    fun executesSuccessfullyMonitor2() {
-        val rule = createRule(listOf(monitorWithoutException, monitorWithoutException))
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(2)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(2)
-    }
-
-    @Test
-    fun executesWithStartFailure() {
-        val failure =
-            assertThrows<TransitionTracingFailure> {
-                val rule = createRule(listOf(monitorWithExceptionStart))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(0)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
-    }
-
-    @Test
-    fun executesStartFailureMonitor2() {
-        val failure =
-            assertThrows<TransitionTracingFailure> {
-                val rule = createRule(listOf(monitorWithExceptionStart, monitorWithoutException))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(0)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(2)
-    }
-
-    @Test
-    fun executesWithStopFailure() {
-        val failure =
-            assertThrows<TransitionTracingFailure> {
-                val rule = createRule(listOf(monitorWithExceptionStop))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(1)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(0)
-    }
-
-    @Test
-    fun executesStopFailureMonitor2() {
-        val failure =
-            assertThrows<TransitionTracingFailure> {
-                val rule = createRule(listOf(monitorWithExceptionStop, monitorWithoutException))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Start executed").that(startExecutionCount).isEqualTo(2)
-        Truth.assertWithMessage("Set result executed").that(setResultExecutionCount).isEqualTo(1)
-    }
-
-    companion object {
-        private fun createRule(traceMonitors: List<ITransitionMonitor>): TraceMonitorRule {
-            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-            return TraceMonitorRule(
-                traceMonitors,
-                TEST_SCENARIO,
-                WindowManagerStateHelper(),
-                ResultWriter(),
-                instrumentation
-            )
-        }
-
-        private fun createMonitor(
-            onStart: () -> Unit,
-            onSetResult: (ResultWriter) -> Unit
-        ): ITransitionMonitor =
-            object : ITransitionMonitor {
-                override fun start() {
-                    onStart()
-                }
-
-                override fun stop(writer: ResultWriter) {
-                    onSetResult(writer)
-                }
-            }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionExecutionRuleTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionExecutionRuleTest.kt
deleted file mode 100644
index 9ab9f3a..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionExecutionRuleTest.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.annotation.SuppressLint
-import android.app.Instrumentation
-import android.os.SystemClock
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.AbstractFlickerTestData
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertExceptionMessage
-import com.android.server.wm.flicker.assertExceptionMessageCause
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.newTestResultWriter
-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
-import org.mockito.Mockito
-
-/** Tests for [TransitionExecutionRule] */
-@SuppressLint("VisibleForTests")
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TransitionExecutionRuleTest {
-    private var executed = false
-
-    private val runTransition: IFlickerTestData.() -> Unit = {
-        executed = true
-        SystemClock.sleep(100)
-    }
-    private val runCreateValidTags: IFlickerTestData.() -> Unit = {
-        createTag(VALID_TAG_1)
-        createTag(VALID_TAG_2)
-    }
-    private val runInvalidTagSpace: IFlickerTestData.() -> Unit = { createTag(INVALID_TAG_SPACE) }
-    private val runInvalidTagUnderscore: IFlickerTestData.() -> Unit = {
-        createTag(INVALID_TAG_UNDERSCORE)
-    }
-    private val throwTransitionError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
-    private val throwAssertionError: IFlickerTestData.() -> Unit = {
-        throw AssertionError(Consts.FAILURE)
-    }
-
-    @Before
-    fun setup() {
-        executed = false
-    }
-
-    @Test
-    fun runSuccessfully() {
-        val rule = createRule(listOf(runTransition))
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        Truth.assertWithMessage("Transition executed").that(executed).isTrue()
-    }
-
-    @Test
-    fun setTransitionStartAndEndTime() {
-        val writer = newTestResultWriter()
-        val rule = createRule(listOf(runTransition), writer)
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        val result = writer.write()
-        TestUtils.validateTransitionTime(result)
-    }
-
-    @Test
-    fun throwsTransitionFailure() {
-        val failure =
-            assertThrows<TransitionExecutionFailure> {
-                val rule = createRule(listOf(throwTransitionError))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        assertExceptionMessageCause(failure, Consts.FAILURE)
-        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
-    }
-
-    @Test
-    fun throwsTransitionFailureEmptyTransitions() {
-        val failure =
-            assertThrows<TransitionExecutionFailure> {
-                val rule = createRule(listOf())
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        assertExceptionMessageCause(failure, EMPTY_TRANSITIONS_ERROR)
-        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
-    }
-
-    @Test
-    fun throwsAssertionFailure() {
-        val failure =
-            assertThrows<AssertionError> {
-                val rule = createRule(listOf(throwAssertionError))
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        Truth.assertWithMessage("Failure").that(failure).hasMessageThat().contains(Consts.FAILURE)
-        Truth.assertWithMessage("Transition executed").that(executed).isFalse()
-    }
-
-    @Test
-    fun createsValidTags() {
-        val writer = newTestResultWriter()
-        val rule = createRule(listOf(runCreateValidTags), writer)
-        rule.apply(base = null, description = Consts.description(this)).evaluate()
-        val result = writer.write()
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-        val wmStateValidTag1 =
-            reader.readWmState(VALID_TAG_1) ?: error("Couldn't parse WM state for $VALID_TAG_1")
-        val wmStateValidTag2 =
-            reader.readWmState(VALID_TAG_2) ?: error("Couldn't parse WM state for $VALID_TAG_2")
-        val layerStateValidTag1 =
-            reader.readLayersDump(VALID_TAG_1) ?: error("Couldn't parse SF state for $VALID_TAG_1")
-        val layerStateValidTag2 =
-            reader.readLayersDump(VALID_TAG_2) ?: error("Couldn't parse SF state for $VALID_TAG_2")
-
-        Truth.assertWithMessage("File count").that(reader.countFiles()).isEqualTo(4)
-        Truth.assertWithMessage("WM State - $VALID_TAG_1")
-            .that(wmStateValidTag1.entries)
-            .isNotEmpty()
-        Truth.assertWithMessage("WM State - $VALID_TAG_2")
-            .that(wmStateValidTag2.entries)
-            .isNotEmpty()
-        Truth.assertWithMessage("SF State - $VALID_TAG_1")
-            .that(layerStateValidTag1.entries)
-            .isNotEmpty()
-        Truth.assertWithMessage("SF State - $VALID_TAG_2")
-            .that(layerStateValidTag2.entries)
-            .isNotEmpty()
-    }
-
-    @Test
-    fun throwErrorCreateInvalidTagWithSpace() {
-        val writer = newTestResultWriter()
-        val failure =
-            assertThrows<TransitionExecutionFailure> {
-                val rule = createRule(listOf(runInvalidTagSpace), writer)
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        assertExceptionMessage(failure, INVALID_TAG_SPACE)
-    }
-
-    @Test
-    fun throwErrorCreateInvalidTagDuplicate() {
-        val writer = newTestResultWriter()
-        val failure =
-            assertThrows<TransitionExecutionFailure> {
-                val rule = createRule(listOf(runCreateValidTags, runCreateValidTags), writer)
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        assertExceptionMessage(failure, VALID_TAG_1)
-    }
-
-    @Test
-    fun throwErrorCreateInvalidTagWithUnderscore() {
-        val writer = newTestResultWriter()
-        val failure =
-            assertThrows<TransitionExecutionFailure> {
-                val rule = createRule(listOf(runInvalidTagUnderscore), writer)
-                rule.apply(base = null, description = Consts.description(this)).evaluate()
-            }
-        assertExceptionMessage(failure, INVALID_TAG_UNDERSCORE)
-    }
-
-    companion object {
-        private const val VALID_TAG_1 = "ValidTag1"
-        private const val VALID_TAG_2 = "Valid_Tag2"
-        private const val INVALID_TAG_SPACE = "Invalid Tag"
-        private const val INVALID_TAG_UNDERSCORE = "Invalid__Tag"
-
-        private fun createRule(
-            commands: List<IFlickerTestData.() -> Unit>,
-            writer: ResultWriter = newTestResultWriter()
-        ): TransitionExecutionRule {
-            val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-            val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
-            return TransitionExecutionRule(
-                mockedFlicker,
-                writer,
-                TEST_SCENARIO,
-                instrumentation,
-                commands,
-                WindowManagerStateHelper()
-            )
-        }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionRunnerTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionRunnerTest.kt
deleted file mode 100644
index 138d998..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/runner/TransitionRunnerTest.kt
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * 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.flicker.runner
-
-import android.annotation.SuppressLint
-import android.app.Instrumentation
-import android.os.SystemClock
-import android.view.WindowManagerGlobal
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.DEFAULT_TRACE_CONFIG
-import com.android.server.wm.flicker.IFlickerTestData
-import com.android.server.wm.flicker.TEST_SCENARIO
-import com.android.server.wm.flicker.assertExceptionMessageCause
-import com.android.server.wm.flicker.createMockedFlicker
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.io.ResultReader
-import com.android.server.wm.flicker.io.ResultWriter
-import com.android.server.wm.flicker.monitor.ITransitionMonitor
-import com.android.server.wm.traces.common.io.RunStatus
-import com.google.common.truth.Truth
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/** Tests for [TransitionRunner] */
-@SuppressLint("VisibleForTests")
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TransitionRunnerTest {
-    private val executionOrder = mutableListOf<String>()
-
-    private val runSetup: IFlickerTestData.() -> Unit = {
-        executionOrder.add(Consts.SETUP)
-        SystemClock.sleep(100)
-    }
-    private val runTeardown: IFlickerTestData.() -> Unit = {
-        executionOrder.add(Consts.TEARDOWN)
-        SystemClock.sleep(100)
-    }
-    private val runTransition: IFlickerTestData.() -> Unit = {
-        executionOrder.add(Consts.TRANSITION)
-        SystemClock.sleep(100)
-    }
-    private val throwError: IFlickerTestData.() -> Unit = { error(Consts.FAILURE) }
-
-    @Before
-    fun setup() {
-        executionOrder.clear()
-        SystemUtil.runShellCommand("rm -rf ${getDefaultFlickerOutputDir()}")
-    }
-
-    @After
-    fun assertTracingStopped() {
-        val windowManager = WindowManagerGlobal.getWindowManagerService()
-        Truth.assertWithMessage("Layers Trace running").that(windowManager.isLayerTracing).isFalse()
-        Truth.assertWithMessage("WM Trace running")
-            .that(windowManager.isWindowTraceEnabled)
-            .isFalse()
-    }
-
-    @Test
-    fun runsTransition() {
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
-        val dummyMonitor = dummyMonitor()
-        val mockedFlicker =
-            createMockedFlicker(
-                setup = listOf(runSetup),
-                teardown = listOf(runTeardown),
-                transitions = listOf(runTransition),
-                extraMonitor = dummyMonitor
-            )
-        val result = runner.execute(mockedFlicker, Consts.description(this))
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        validateExecutionOrder(hasTransition = true)
-        dummyMonitor.validate()
-        TestUtils.validateTransitionTime(result)
-        Truth.assertWithMessage("Run status")
-            .that(reader.runStatus)
-            .isEqualTo(RunStatus.RUN_EXECUTED)
-    }
-
-    @Test
-    fun failsWithNoTransitions() {
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
-        val dummyMonitor = dummyMonitor()
-        val mockedFlicker =
-            createMockedFlicker(
-                setup = listOf(runSetup),
-                teardown = listOf(runTeardown),
-                extraMonitor = dummyMonitor
-            )
-        val result = runner.execute(mockedFlicker, Consts.description(this))
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        validateExecutionOrder(hasTransition = false)
-        dummyMonitor.validate()
-        TestUtils.validateTransitionTime(result)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertExceptionMessageCause(result.executionError, EMPTY_TRANSITIONS_ERROR)
-    }
-
-    @Test
-    fun failsWithTransitionError() {
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
-        val dummyMonitor = dummyMonitor()
-        val mockedFlicker =
-            createMockedFlicker(
-                setup = listOf(runSetup),
-                teardown = listOf(runTeardown),
-                transitions = listOf(throwError),
-                extraMonitor = dummyMonitor
-            )
-        val result = runner.execute(mockedFlicker, Consts.description(this))
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        validateExecutionOrder(hasTransition = false)
-        dummyMonitor.validate()
-        TestUtils.validateTransitionTime(result)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
-    }
-
-    @Test
-    fun failsWithSetupErrorAndHasTraces() {
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
-        val dummyMonitor = dummyMonitor()
-        val mockedFlicker =
-            createMockedFlicker(
-                setup = listOf(runSetup, throwError),
-                teardown = listOf(runTeardown),
-                transitions = listOf(runTransition),
-                extraMonitor = dummyMonitor
-            )
-        val result = runner.execute(mockedFlicker, Consts.description(this))
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        validateExecutionOrder(hasTransition = false)
-        dummyMonitor.validate()
-        TestUtils.validateTransitionTimeIsEmpty(result)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
-    }
-
-    @Test
-    fun failsWithTeardownErrorAndHasTraces() {
-        val runner = TransitionRunner(TEST_SCENARIO, instrumentation, ResultWriter())
-        val dummyMonitor = dummyMonitor()
-        val mockedFlicker =
-            createMockedFlicker(
-                setup = listOf(runSetup),
-                teardown = listOf(runTeardown, throwError),
-                transitions = listOf(runTransition),
-                extraMonitor = dummyMonitor
-            )
-        val result = runner.execute(mockedFlicker, Consts.description(this))
-        val reader = ResultReader(result, DEFAULT_TRACE_CONFIG)
-
-        validateExecutionOrder(hasTransition = true)
-        dummyMonitor.validate()
-        TestUtils.validateTransitionTime(result)
-        Truth.assertWithMessage("Run status").that(reader.runStatus).isEqualTo(RunStatus.RUN_FAILED)
-        assertExceptionMessageCause(result.executionError, Consts.FAILURE)
-    }
-
-    private fun assertContainsOrNot(value: String, hasValue: Boolean): String? {
-        return if (hasValue) {
-            Truth.assertWithMessage("$value executed").that(executionOrder).contains(value)
-            value
-        } else {
-            Truth.assertWithMessage("$value skipped").that(executionOrder).doesNotContain(value)
-            null
-        }
-    }
-
-    private fun validateExecutionOrder(hasTransition: Boolean) {
-        val expected = mutableListOf<String>()
-        assertContainsOrNot(Consts.SETUP, hasValue = true)?.also { expected.add(it) }
-        assertContainsOrNot(Consts.TRANSITION, hasTransition)?.also { expected.add(it) }
-        assertContainsOrNot(Consts.TEARDOWN, hasValue = true)?.also { expected.add(it) }
-
-        Truth.assertWithMessage("Execution order")
-            .that(executionOrder)
-            .containsExactlyElementsIn(expected)
-            .inOrder()
-    }
-
-    private fun dummyMonitor() =
-        object : ITransitionMonitor {
-            private var startExecuted = false
-            private var setResultExecuted = false
-
-            override fun start() {
-                startExecuted = true
-            }
-
-            override fun stop(writer: ResultWriter) {
-                setResultExecuted = true
-            }
-
-            fun validate() {
-                Truth.assertWithMessage("Start executed").that(startExecuted).isTrue()
-                Truth.assertWithMessage("Set result executed").that(setResultExecuted).isTrue()
-            }
-        }
-
-    companion object {
-        private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollectorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollectorTest.kt
deleted file mode 100644
index f36b79f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceResultsCollectorTest.kt
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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.flicker.service
-
-import android.device.collectors.DataRecord
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.io.ParsedTracesReader
-import com.android.server.wm.flicker.utils.KotlinMockito
-import com.android.server.wm.flicker.utils.MockLayersTraceBuilder
-import com.android.server.wm.flicker.utils.MockWindowManagerTraceBuilder
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-import com.android.server.wm.traces.common.service.IFlickerService
-import com.android.server.wm.traces.common.service.ITracesCollector
-import com.android.server.wm.traces.common.service.assertors.AssertionResult
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-import com.android.server.wm.traces.common.transition.TransitionsTrace
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.Description
-import org.junit.runner.notification.Failure
-import org.junit.runners.MethodSorters
-import org.mockito.Mockito
-
-/**
- * Contains [FlickerServiceResultsCollector] tests. To run this test: `atest
- * FlickerLibTest:FlickerServiceResultsCollectorTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FlickerServiceResultsCollectorTest {
-    @Test
-    fun reportsMetricsOnlyForPassingTestsIfRequested() {
-        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
-        Mockito.`when`(mockTraceCollector.getResultReader())
-            .thenReturn(
-                ParsedTracesReader(
-                    wmTrace = MockWindowManagerTraceBuilder().build(),
-                    layersTrace = MockLayersTraceBuilder().build(),
-                    transitionsTrace = TransitionsTrace(emptyArray()),
-                    transactionsTrace = null
-                )
-            )
-        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
-        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
-            .thenReturn(listOf(mockSuccessfulAssertionResult))
-
-        val collector =
-            FlickerServiceResultsCollector(
-                tracesCollector = mockTraceCollector,
-                flickerService = mockFlickerService,
-                reportOnlyForPassingTests = true,
-            )
-
-        val runData = DataRecord()
-        val runDescription = Description.createSuiteDescription("TestSuite")
-        val testData = DataRecord()
-        val testDescription = Description.createTestDescription("TestClass", "TestName")
-
-        collector.onTestRunStart(runData, runDescription)
-        collector.onTestStart(testData, testDescription)
-        collector.onTestFail(testData, testDescription, Mockito.mock(Failure::class.java))
-        collector.onTestEnd(testData, testDescription)
-        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
-
-        Truth.assertThat(collector.executionErrors).isEmpty()
-        Truth.assertThat(collector.assertionResultsByTest[testDescription]).isNull()
-        Truth.assertThat(runData.hasMetrics()).isFalse()
-    }
-
-    @Test
-    fun reportsMetricsForFailingTestsIfRequested() {
-        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
-        Mockito.`when`(mockTraceCollector.getResultReader())
-            .thenReturn(
-                ParsedTracesReader(
-                    wmTrace = MockWindowManagerTraceBuilder().build(),
-                    layersTrace = MockLayersTraceBuilder().build(),
-                    transitionsTrace = TransitionsTrace(emptyArray()),
-                    transactionsTrace = null
-                )
-            )
-        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
-        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
-            .thenReturn(listOf(mockSuccessfulAssertionResult))
-        val collector =
-            FlickerServiceResultsCollector(
-                tracesCollector = mockTraceCollector,
-                flickerService = mockFlickerService,
-                reportOnlyForPassingTests = false,
-            )
-
-        val runData = DataRecord()
-        val runDescription = Description.createSuiteDescription("TestSuite")
-        val testData = DataRecord()
-        val testDescription = Description.createTestDescription("TestClass", "TestName")
-
-        collector.onTestRunStart(runData, runDescription)
-        collector.onTestStart(testData, testDescription)
-        collector.onTestFail(testData, testDescription, Mockito.mock(Failure::class.java))
-        collector.onTestEnd(testData, testDescription)
-        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
-
-        Truth.assertThat(collector.executionErrors).isEmpty()
-        Truth.assertThat(collector.resultsForTest(testDescription)).isNotEmpty()
-        Truth.assertThat(testData.hasMetrics()).isTrue()
-    }
-
-    @Test
-    fun collectsMetricsForEachTestIfRequested() {
-        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
-        Mockito.`when`(mockTraceCollector.getResultReader())
-            .thenReturn(
-                ParsedTracesReader(
-                    wmTrace = MockWindowManagerTraceBuilder().build(),
-                    layersTrace = MockLayersTraceBuilder().build(),
-                    transitionsTrace = TransitionsTrace(emptyArray()),
-                    transactionsTrace = null
-                )
-            )
-        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
-        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
-            .thenReturn(listOf(mockSuccessfulAssertionResult))
-        val collector =
-            FlickerServiceResultsCollector(
-                tracesCollector = mockTraceCollector,
-                flickerService = mockFlickerService,
-                collectMetricsPerTest = true,
-            )
-
-        val runData = DataRecord()
-        val runDescription = Description.createSuiteDescription("TestSuite")
-        val testData = DataRecord()
-        val testDescription = Description.createTestDescription("TestClass", "TestName")
-
-        collector.onTestRunStart(runData, runDescription)
-        collector.onTestStart(testData, testDescription)
-        collector.onTestEnd(testData, testDescription)
-        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
-
-        Truth.assertThat(collector.executionErrors).isEmpty()
-        Truth.assertThat(collector.resultsForTest(testDescription)).isNotEmpty()
-        Truth.assertThat(testData.hasMetrics()).isTrue()
-    }
-
-    @Test
-    fun collectsMetricsForEntireTestRunIfRequested() {
-        val mockTraceCollector = Mockito.mock(ITracesCollector::class.java)
-        Mockito.`when`(mockTraceCollector.getResultReader())
-            .thenReturn(
-                ParsedTracesReader(
-                    wmTrace = MockWindowManagerTraceBuilder().build(),
-                    layersTrace = MockLayersTraceBuilder().build(),
-                    transitionsTrace = TransitionsTrace(emptyArray()),
-                    transactionsTrace = null
-                )
-            )
-        val mockFlickerService = Mockito.mock(IFlickerService::class.java)
-        Mockito.`when`(mockFlickerService.process(KotlinMockito.any(IReader::class.java)))
-            .thenReturn(listOf(mockSuccessfulAssertionResult))
-        val collector =
-            FlickerServiceResultsCollector(
-                tracesCollector = mockTraceCollector,
-                flickerService = mockFlickerService,
-                collectMetricsPerTest = false,
-            )
-
-        val runData = DataRecord()
-        val runDescription = Description.createSuiteDescription("TestSuite")
-        val testData = DataRecord()
-        val testDescription = Description.createTestDescription("TestClass", "TestName")
-
-        collector.onTestRunStart(runData, runDescription)
-        collector.onTestStart(testData, testDescription)
-        collector.onTestEnd(testData, testDescription)
-        collector.onTestRunEnd(runData, Mockito.mock(org.junit.runner.Result::class.java))
-
-        Truth.assertThat(collector.executionErrors).isEmpty()
-        Truth.assertThat(collector.assertionResults).isNotEmpty()
-        Truth.assertThat(runData.hasMetrics()).isTrue()
-    }
-
-    companion object {
-        val mockSuccessfulAssertionResult =
-            AssertionResult(
-                object : IFaasAssertion {
-                    override val name: String
-                        get() = "MockAssertion"
-                    override val stabilityGroup: AssertionInvocationGroup
-                        get() = AssertionInvocationGroup.BLOCKING
-                    override fun evaluate(): IAssertionResult {
-                        error("Unimplemented - shouldn't be called")
-                    }
-                },
-                assertionError = null
-            )
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceRuleTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceRuleTest.kt
deleted file mode 100644
index 2b821dd..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceRuleTest.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * 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.flicker.service
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.runner.Consts
-import com.android.server.wm.flicker.runner.ExecutionError
-import com.android.server.wm.flicker.service.rules.FlickerServiceRule
-import com.android.server.wm.flicker.utils.KotlinMockito
-import com.android.server.wm.traces.common.service.AssertionInvocationGroup
-import com.android.server.wm.traces.common.service.assertors.AssertionResult
-import com.android.server.wm.traces.common.service.assertors.IAssertionResult
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-import com.google.common.truth.Truth
-import org.junit.Assume
-import org.junit.AssumptionViolatedException
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.Description
-import org.junit.runners.MethodSorters
-import org.mockito.Mockito
-import org.mockito.Mockito.`when`
-
-/**
- * Contains [FlickerServiceRule] tests. To run this test: `atest
- * FlickerLibTest:FlickerServiceRuleTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FlickerServiceRuleTest {
-    @Before
-    fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    @Test
-    fun startsTraceCollectionOnTestStarting() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-
-        testRule.starting(mockDescription)
-        Mockito.verify(mockFlickerServiceResultsCollector).testStarted(mockDescription)
-    }
-
-    @Test
-    fun stopsTraceCollectionOnTestFinished() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-
-        testRule.finished(mockDescription)
-        Mockito.verify(mockFlickerServiceResultsCollector).testFinished(mockDescription)
-    }
-
-    @Test
-    fun reportsFailuresToMetricsCollector() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-        val mockError = Throwable("Mock error")
-
-        testRule.failed(mockError, mockDescription)
-        Mockito.verify(mockFlickerServiceResultsCollector)
-            .testFailure(
-                KotlinMockito.argThat {
-                    this.description == mockDescription && this.exception == mockError
-                }
-            )
-    }
-
-    @Test
-    fun reportsSkippedToMetricsCollector() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule = FlickerServiceRule(mockFlickerServiceResultsCollector)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-        val mockAssumptionFailure = AssumptionViolatedException("Mock error")
-
-        testRule.skipped(mockAssumptionFailure, mockDescription)
-        Mockito.verify(mockFlickerServiceResultsCollector).testSkipped(mockDescription)
-    }
-
-    @Test
-    fun doesNotThrowExceptionForFlickerTestFailureIfRequested() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule =
-            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = false)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-
-        val assertionError = Throwable("Some assertion error")
-        `when`(mockFlickerServiceResultsCollector.resultsForTest(mockDescription))
-            .thenReturn(listOf(mockFailureAssertionResult(assertionError)))
-        `when`(mockFlickerServiceResultsCollector.testContainsFlicker(mockDescription))
-            .thenReturn(true)
-
-        testRule.starting(mockDescription)
-        testRule.succeeded(mockDescription)
-        testRule.finished(mockDescription)
-    }
-
-    @Test
-    fun throwsExceptionForFlickerTestFailureIfRequested() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule =
-            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = true)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-
-        val assertionError = Throwable("Some assertion error")
-        `when`(mockFlickerServiceResultsCollector.resultsForTest(mockDescription))
-            .thenReturn(listOf(mockFailureAssertionResult(assertionError)))
-        `when`(mockFlickerServiceResultsCollector.testContainsFlicker(mockDescription))
-            .thenReturn(true)
-
-        testRule.starting(mockDescription)
-        testRule.succeeded(mockDescription)
-        try {
-            testRule.finished(mockDescription)
-            error("Exception was not thrown")
-        } catch (e: Throwable) {
-            Truth.assertThat(e).isEqualTo(assertionError)
-        }
-    }
-
-    @Test
-    fun alwaysThrowsExceptionForExecutionErrors() {
-        val mockFlickerServiceResultsCollector =
-            Mockito.mock(IFlickerServiceResultsCollector::class.java)
-        val testRule =
-            FlickerServiceRule(mockFlickerServiceResultsCollector, failTestOnFaasFailure = true)
-        val mockDescription = Description.createTestDescription("MockClass", "mockTest")
-
-        val executionError = ExecutionError(Throwable(Consts.FAILURE))
-        `when`(mockFlickerServiceResultsCollector.executionErrors)
-            .thenReturn(listOf(executionError))
-
-        testRule.starting(mockDescription)
-        testRule.succeeded(mockDescription)
-        try {
-            testRule.finished(mockDescription)
-            error("Exception was not thrown")
-        } catch (e: Throwable) {
-            Truth.assertThat(e).isEqualTo(executionError)
-        }
-    }
-
-    companion object {
-        fun mockFailureAssertionResult(error: Throwable): IAssertionResult {
-            return AssertionResult(
-                object : IFaasAssertion {
-                    override val name: String
-                        get() = "MockAssertion"
-                    override val stabilityGroup: AssertionInvocationGroup
-                        get() = AssertionInvocationGroup.BLOCKING
-                    override fun evaluate(): IAssertionResult {
-                        error("Unimplemented - shouldn't be called")
-                    }
-                },
-                assertionError = error
-            )
-        }
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTest.kt
deleted file mode 100644
index aa59cee..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTest.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.service
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.IScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.IFaasAssertion
-import com.android.server.wm.traces.common.service.assertors.factories.IAssertionFactory
-import com.android.server.wm.traces.common.service.assertors.runners.IAssertionRunner
-import com.android.server.wm.traces.common.service.extractors.IScenarioExtractor
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-
-/** Contains [FlickerService] tests. To run this test: `atest FlickerLibTest:FlickerServiceTest` */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FlickerServiceTest {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val wmHelper = WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
-
-    @Test
-    fun generatesAssertionsFromExtractedScenarios() {
-        val mockReader = Mockito.mock(IReader::class.java)
-        val mockScenarioExtractor = Mockito.mock(IScenarioExtractor::class.java)
-        val mockAssertionFactory = Mockito.mock(IAssertionFactory::class.java)
-        val mockAssertionRunner = Mockito.mock(IAssertionRunner::class.java)
-
-        val scenarioInstance = Mockito.mock(IScenarioInstance::class.java)
-        val assertions = listOf(Mockito.mock(IFaasAssertion::class.java))
-
-        Mockito.`when`(mockScenarioExtractor.extract(mockReader))
-            .thenReturn(listOf(scenarioInstance))
-        Mockito.`when`(mockAssertionFactory.generateAssertionsFor(scenarioInstance))
-            .thenReturn(assertions)
-
-        val service =
-            FlickerService(
-                scenarioExtractor = mockScenarioExtractor,
-                assertionFactory = mockAssertionFactory,
-                assertionRunner = mockAssertionRunner
-            )
-        service.process(mockReader)
-
-        Mockito.verify(mockScenarioExtractor).extract(mockReader)
-        Mockito.verify(mockAssertionFactory).generateAssertionsFor(scenarioInstance)
-    }
-
-    @Test
-    fun executesAssertionsReturnedByAssertionFactories() {
-        val mockReader = Mockito.mock(IReader::class.java)
-        val mockScenarioExtractor = Mockito.mock(IScenarioExtractor::class.java)
-        val mockAssertionFactory = Mockito.mock(IAssertionFactory::class.java)
-        val mockAssertionRunner = Mockito.mock(IAssertionRunner::class.java)
-
-        val scenarioInstance = Mockito.mock(IScenarioInstance::class.java)
-        val assertions = listOf(Mockito.mock(IFaasAssertion::class.java))
-
-        Mockito.`when`(mockScenarioExtractor.extract(mockReader))
-            .thenReturn(listOf(scenarioInstance))
-        Mockito.`when`(mockAssertionFactory.generateAssertionsFor(scenarioInstance))
-            .thenReturn(assertions)
-
-        val service =
-            FlickerService(
-                scenarioExtractor = mockScenarioExtractor,
-                assertionFactory = mockAssertionFactory,
-                assertionRunner = mockAssertionRunner
-            )
-        service.process(mockReader)
-
-        Mockito.verify(mockScenarioExtractor).extract(mockReader)
-        Mockito.verify(mockAssertionRunner).execute(assertions)
-    }
-
-    fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-
-    inline fun <reified T : Any> argumentCaptor() = ArgumentCaptor.forClass(T::class.java)
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollectorTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollectorTest.kt
deleted file mode 100644
index d2c602e..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/service/FlickerServiceTracesCollectorTest.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.flicker.service
-
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.assertArchiveContainsFiles
-import com.android.server.wm.flicker.getDefaultFlickerOutputDir
-import com.android.server.wm.flicker.helpers.BrowserAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.google.common.truth.Truth
-import java.io.File
-import org.junit.Assume
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [FlickerServiceTracesCollector] tests. To run this test: `atest
- * FlickerLibTest:FlickerServiceTracesCollectorTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FlickerServiceTracesCollectorTest {
-    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp: BrowserAppHelper = BrowserAppHelper(instrumentation)
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    @Test
-    fun canCollectTraces() {
-        val wmHelper = WindowManagerStateHelper(instrumentation)
-        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
-        collector.start()
-        testApp.launchViaIntent(wmHelper)
-        testApp.exit(wmHelper)
-        collector.stop()
-        val reader = collector.getResultReader()
-
-        Truth.assertThat(reader.readWmTrace()?.entries ?: emptyArray()).isNotEmpty()
-        Truth.assertThat(reader.readLayersTrace()?.entries ?: emptyArray()).isNotEmpty()
-        Truth.assertThat(reader.readTransitionsTrace()?.entries ?: emptyArray()).isNotEmpty()
-    }
-
-    @Test
-    fun reportsTraceFile() {
-        val wmHelper = WindowManagerStateHelper(instrumentation)
-        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
-        collector.start()
-        testApp.launchViaIntent(wmHelper)
-        testApp.exit(wmHelper)
-        collector.stop()
-        val tracePath = collector.getResultReader().artifactPath
-
-        require(tracePath.isNotEmpty()) { "Artifact path missing in result" }
-        val traceFile = File(tracePath)
-        Truth.assertThat(traceFile.exists()).isTrue()
-    }
-
-    @Test
-    fun reportedTraceFileContainsAllTraces() {
-        val wmHelper = WindowManagerStateHelper(instrumentation)
-        val collector = FlickerServiceTracesCollector(getDefaultFlickerOutputDir())
-        collector.start()
-        testApp.launchViaIntent(wmHelper)
-        testApp.exit(wmHelper)
-        collector.stop()
-        val tracePath = collector.getResultReader().artifactPath
-
-        require(tracePath.isNotEmpty()) { "Artifact path missing in result" }
-        val traceFile = File(tracePath)
-        assertArchiveContainsFiles(traceFile, expectedTraces)
-    }
-
-    companion object {
-        val expectedTraces =
-            listOf(
-                "wm_trace.winscope",
-                "layers_trace.winscope",
-                "transactions_trace.winscope",
-                "transition_trace.winscope",
-                "eventlog.winscope"
-            )
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/factories/AssertionFactoryTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/factories/AssertionFactoryTest.kt
deleted file mode 100644
index 907fb08..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/service/assertors/factories/AssertionFactoryTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 com.android.server.wm.flicker.service.assertors.factories
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.io.IReader
-import com.android.server.wm.traces.common.service.PlatformConsts
-import com.android.server.wm.traces.common.service.ScenarioInstance
-import com.android.server.wm.traces.common.service.assertors.factories.AssertionFactory
-import com.android.server.wm.traces.common.service.config.FaasScenarioType
-import com.android.server.wm.traces.common.service.config.FlickerServiceConfig
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.Test
-import org.mockito.Mockito
-
-/**
- * Contains tests for the [AssertionFactory]. To run this test: `atest
- * FlickerLibTest:AssertionFactoryTest`
- */
-class AssertionFactoryTest {
-
-    @Test
-    fun getsAssertionsFromConfig() {
-        val factory = AssertionFactory()
-
-        val type = FaasScenarioType.LAUNCHER_APP_LAUNCH_FROM_ICON
-
-        val scenarioInstance =
-            ScenarioInstance(
-                type = type,
-                startRotation = PlatformConsts.Rotation.ROTATION_0,
-                endRotation = PlatformConsts.Rotation.ROTATION_0,
-                startTimestamp = CrossPlatform.timestamp.min(),
-                endTimestamp = CrossPlatform.timestamp.max(),
-                reader = Mockito.mock(IReader::class.java)
-            )
-        val assertions = factory.generateAssertionsFor(scenarioInstance)
-
-        Truth.assertThat(assertions.map { it.name })
-            .containsExactlyElementsIn(
-                FlickerServiceConfig.getScenarioConfigFor(type = type).assertionTemplates.map {
-                    it.assertionName
-                }
-            )
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/KotlinMockito.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/KotlinMockito.kt
deleted file mode 100644
index 1072fcc..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/KotlinMockito.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.flicker.utils
-
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito
-
-class KotlinMockito {
-    companion object {
-        inline fun <T> any(type: Class<T>): T {
-            Mockito.any(type)
-            return uninitialized()
-        }
-
-        inline fun <reified T : Any> argThat(noinline predicate: T.() -> Boolean): T {
-            return ArgumentMatchers.argThat { arg: T? -> arg?.predicate() ?: false }
-                ?: uninitialized()
-        }
-
-        inline fun <T> uninitialized(): T = null as T
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerBuilder.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerBuilder.kt
deleted file mode 100644
index d4d0b9e..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerBuilder.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.traces.common.ActiveBuffer
-import com.android.server.wm.traces.common.Color
-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.layers.HwcCompositionType
-import com.android.server.wm.traces.common.layers.ILayerProperties
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.layers.Transform
-import com.android.server.wm.traces.common.region.Region
-
-class MockLayerBuilder(private val name: String) {
-    companion object {
-        private var idCounter = 1
-    }
-
-    private val children = mutableListOf<MockLayerBuilder>()
-    private var type = "BufferStateLayer"
-    private val id = idCounter++
-    private var parentId = -1
-    private var isVisible = true
-    private var absoluteBounds: Rect? = null
-    private var zIndex = 0
-    private var isOpaque = true
-
-    fun addChild(layer: MockLayerBuilder): MockLayerBuilder = apply { this.children.add(layer) }
-
-    fun setContainerLayer(): MockLayerBuilder = apply {
-        this.type = "ContainerLayer"
-        this.isOpaque = false
-        this.isVisible = false
-    }
-
-    fun setVisible(): MockLayerBuilder = apply { this.isVisible = true }
-
-    fun setInvisible(): MockLayerBuilder = apply { this.isVisible = false }
-
-    fun addChildren(rootLayers: Collection<MockLayerBuilder>): MockLayerBuilder = apply {
-        rootLayers.forEach { addChild(it) }
-    }
-
-    fun setAbsoluteBounds(bounds: Rect): MockLayerBuilder = apply { this.absoluteBounds = bounds }
-
-    fun build(): Layer {
-        val absoluteBounds = this.absoluteBounds
-        requireNotNull(absoluteBounds) { "Layer has no bounds set..." }
-
-        val transform = Transform.from(0, Matrix33.identity(0f, 0f))
-
-        val thisLayer =
-            Layer.from(
-                name,
-                id,
-                parentId,
-                z = zIndex,
-                visibleRegion = if (isVisible) Region.from(absoluteBounds) else Region.EMPTY,
-                activeBuffer = ActiveBuffer.from(absoluteBounds.width, absoluteBounds.height, 1, 1),
-                flags = if (isVisible) 0 else ILayerProperties.Flag.HIDDEN.value,
-                bounds = absoluteBounds.toRectF(),
-                color = Color.DEFAULT,
-                isOpaque = isVisible && isOpaque,
-                shadowRadius = 0f,
-                cornerRadius = 0f,
-                type = type,
-                screenBounds = absoluteBounds.toRectF(),
-                transform = transform,
-                sourceBounds = absoluteBounds.toRectF(),
-                currFrame = 0,
-                effectiveScalingMode = 0,
-                bufferTransform = transform,
-                hwcCompositionType = HwcCompositionType.INVALID,
-                hwcCrop = RectF.EMPTY,
-                hwcFrame = Rect.EMPTY,
-                crop = absoluteBounds,
-                backgroundBlurRadius = 0,
-                isRelativeOf = false,
-                zOrderRelativeOfId = -1,
-                stackId = 0,
-                requestedTransform = transform,
-                requestedColor = Color.DEFAULT,
-                cornerRadiusCrop = RectF.EMPTY,
-                inputTransform = transform,
-                inputRegion = Region.from(absoluteBounds),
-                excludesCompositionState = false
-            )
-
-        val layers = mutableListOf<Layer>()
-        layers.add(thisLayer)
-
-        // var indexCount = 1
-        children.forEach { child ->
-            child.parentId = this.id
-            // it.zIndex = this.zIndex + indexCount
-
-            val childAbsoluteBounds = child.absoluteBounds
-            if (childAbsoluteBounds == null) {
-                child.absoluteBounds = this.absoluteBounds
-            } else {
-                child.absoluteBounds = childAbsoluteBounds.intersection(absoluteBounds)
-            }
-
-            thisLayer.addChild(child.build())
-        }
-
-        return thisLayer
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceBuilderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceBuilderTest.kt
deleted file mode 100644
index 64f0858..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceBuilderTest.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.Rect
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Test for the MockLayerTraceBuilder utilities. To run this test: `atest
- * FlickerLibTest:MockLayerTraceBuilderTest`
- */
-class MockLayerTraceBuilderTest {
-    @Test
-    fun containerLayerIsInvisible() {
-        val mockLayer =
-            MockLayerBuilder("Mock Layer")
-                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                .setContainerLayer()
-                .build()
-
-        Truth.assertThat(mockLayer.isVisible).isFalse()
-    }
-
-    @Test
-    fun childrenLayerInheritsParentBounds() {
-        val mockLayer =
-            MockLayerBuilder("Parent Mock Layer")
-                .setContainerLayer()
-                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                .addChild(MockLayerBuilder("Child Mock Layer"))
-                .build()
-
-        Truth.assertThat(mockLayer.children[0].screenBounds).isEqualTo(mockLayer.screenBounds)
-        Truth.assertThat(mockLayer.children[0].bounds).isEqualTo(mockLayer.bounds)
-    }
-
-    @Test
-    fun canAddChildLayer() {
-        val mockLayer =
-            MockLayerBuilder("Parent Mock Layer")
-                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                .addChild(MockLayerBuilder("Child Mock Layer"))
-                .build()
-
-        Truth.assertThat(mockLayer.children).isNotEmpty()
-    }
-
-    @Test
-    fun canSetLayerVisibility() {
-        val mockLayer =
-            MockLayerBuilder("Mock Layer")
-                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                .setInvisible()
-                .build()
-
-        Truth.assertThat(mockLayer.isVisible).isFalse()
-    }
-
-    @Test
-    fun invisibleLayerHasNoVisibleBounds() {
-        val mockLayer =
-            MockLayerBuilder("Mock Layer")
-                .setAbsoluteBounds(Rect.from(0, 0, 200, 200))
-                .setInvisible()
-                .build()
-
-        Truth.assertThat(mockLayer.visibleRegion?.isEmpty ?: true).isTrue()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceEntryBuilder.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceEntryBuilder.kt
deleted file mode 100644
index e251b0c..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayerTraceEntryBuilder.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.Size
-import com.android.server.wm.traces.common.layers.Display
-import com.android.server.wm.traces.common.layers.Layer
-import com.android.server.wm.traces.common.layers.LayerTraceEntry
-import com.android.server.wm.traces.common.layers.Transform
-
-class MockLayerTraceEntryBuilder() {
-    private val displays = mutableListOf<Display>()
-    private val layers = mutableListOf<Layer>()
-    private val bounds = Rect.from(0, 0, 1080, 1920)
-    var timestamp = -1L
-        private set
-
-    constructor(timestamp: Long) : this() {
-        setTimestamp(timestamp)
-    }
-
-    init {
-        if (timestamp <= 0L) {
-            timestamp = ++lastTimestamp
-        }
-    }
-
-    fun addDisplay(rootLayers: List<MockLayerBuilder>): MockLayerTraceEntryBuilder = apply {
-        val displayLayer =
-            MockLayerBuilder("Display").setAbsoluteBounds(bounds).addChildren(rootLayers).build()
-        val displayId = 1UL
-        val stackId = 1
-        this.displays.add(
-            Display.from(
-                id = displayId,
-                name = "Display",
-                layerStackId = stackId,
-                size = Size.from(bounds.width, bounds.height),
-                layerStackSpace = bounds,
-                transform = Transform.EMPTY,
-                isVirtual = false
-            )
-        )
-        this.layers.add(displayLayer)
-    }
-
-    fun setTimestamp(timestamp: Long): MockLayerTraceEntryBuilder = apply {
-        require(timestamp > 0) { "Timestamp must be a positive value." }
-        this.timestamp = timestamp
-        lastTimestamp = timestamp
-    }
-
-    fun build(): LayerTraceEntry {
-        return LayerTraceEntry(
-            elapsedTimestamp = timestamp,
-            clockTimestamp = null,
-            hwcBlob = "",
-            where = "",
-            displays = displays.toTypedArray(),
-            vSyncId = 100,
-            _rootLayers = layers.toTypedArray()
-        )
-    }
-
-    companion object {
-        private var lastTimestamp = 1L
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayersTraceBuilder.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayersTraceBuilder.kt
deleted file mode 100644
index bb388ce..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockLayersTraceBuilder.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
-import com.android.server.wm.traces.common.layers.LayersTrace
-
-class MockLayersTraceBuilder(
-    private var entries: MutableList<MockLayerTraceEntryBuilder> = mutableListOf()
-) {
-    fun addEntry(entry: MockLayerTraceEntryBuilder) {
-        entries.add(entry)
-    }
-
-    fun sortEntriesBasedOfCurrentTimestamps() {
-        entries.sortBy { it.timestamp }
-    }
-
-    fun build(): LayersTrace {
-        require(entries.zipWithNext { prev, cur -> prev.timestamp < cur.timestamp }.all { it }) {
-            "Timestamps not strictly increasing between entries."
-        }
-
-        val entries = entries.map { it.build() }.toTypedArray<BaseLayerTraceEntry>()
-        return LayersTrace(entries)
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowManagerTraceBuilder.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowManagerTraceBuilder.kt
deleted file mode 100644
index 611c469..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowManagerTraceBuilder.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-
-class MockWindowManagerTraceBuilder(
-    private var entries: MutableList<MockWindowStateBuilder> = mutableListOf()
-) {
-    fun addEntry(entry: MockWindowStateBuilder) {
-        entries.add(entry)
-    }
-
-    fun sortEntriesBasedOfCurrentTimestamps() {
-        entries.sortBy { it.timestamp }
-    }
-
-    fun build(): WindowManagerTrace {
-        require(entries.zipWithNext { prev, cur -> prev.timestamp < cur.timestamp }.all { it }) {
-            "Timestamps not strictly increasing between entries."
-        }
-
-        val entries = entries.map { it.build() }.toTypedArray()
-        return WindowManagerTrace(entries)
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowStateBuilder.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowStateBuilder.kt
deleted file mode 100644
index 9a6ae76..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/utils/MockWindowStateBuilder.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.flicker.utils
-
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-
-class MockWindowStateBuilder() {
-    var timestamp = -1L
-        private set
-
-    constructor(timestamp: Long) : this() {
-        setTimestamp(timestamp)
-    }
-
-    init {
-        if (timestamp <= 0L) {
-            timestamp = ++lastTimestamp
-        }
-    }
-
-    fun setTimestamp(timestamp: Long): MockWindowStateBuilder = apply {
-        require(timestamp > 0) { "Timestamp must be a positive value." }
-        this.timestamp = timestamp
-        lastTimestamp = timestamp
-    }
-
-    fun build(): WindowManagerState {
-        return WindowManagerState(
-            elapsedTimestamp = timestamp,
-            clockTimestamp = null,
-            where = "where",
-            policy = null,
-            focusedApp = "focusedApp",
-            focusedDisplayId = 1,
-            _focusedWindow = "focusedWindow",
-            inputMethodWindowAppToken = "",
-            isHomeRecentsComponent = false,
-            isDisplayFrozen = false,
-            _pendingActivities = emptyArray(),
-            root =
-                RootWindowContainer(
-                    WindowContainer(
-                        title = "root container",
-                        token = "",
-                        orientation = 1,
-                        layerId = 1,
-                        _isVisible = true,
-                        configurationContainer = ConfigurationContainer(null, null, null),
-                        children = emptyArray(),
-                        computedZ = 0
-                    )
-                ),
-            keyguardControllerState =
-                KeyguardControllerState.from(
-                    isAodShowing = false,
-                    isKeyguardShowing = false,
-                    keyguardOccludedStates = emptyMap()
-                )
-        )
-    }
-
-    companion object {
-        private var lastTimestamp = 1L
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt
deleted file mode 100644
index 1af16b3..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateSubjectTest.kt
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.assertFailureFact
-import com.android.server.wm.flicker.assertThatErrorContainsDebugInfo
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readWmTraceFromDumpFile
-import com.android.server.wm.flicker.readWmTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.common.subjects.FlickerSubject
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-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 [WindowManagerStateSubject] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerStateSubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerStateSubjectTest {
-    private val trace
-        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
-
-    // Launcher is visible in fullscreen in the first frame of the trace
-    private val traceFirstFrameTimestamp = 9213763541297
-
-    // The first frame where the chrome splash screen is shown
-    private val traceFirstChromeFlashScreenTimestamp = 9215551505798
-
-    // The bounds of the display used to generate the trace [trace]
-    private val displayBounds = Region.from(0, 0, 1440, 2960)
-
-    // The region covered by the status bar in the trace
-    private val statusBarRegion = Region.from(0, 0, 1440, 171)
-
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val error =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerTraceSubject(trace).first().visibleRegion(TestComponents.IMAGINARY)
-            }
-        assertThatErrorContainsDebugInfo(error)
-        Truth.assertThat(error).hasMessageThat().contains(TestComponents.IMAGINARY.className)
-        Truth.assertThat(error).hasMessageThat().contains(FlickerSubject.ASSERTION_TAG)
-    }
-
-    @Test
-    fun canDetectAboveAppWindowVisibility_isVisible() {
-        WindowManagerTraceSubject(trace)
-            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-            .containsAboveAppWindow(ComponentNameMatcher.NAV_BAR)
-            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
-            .containsAboveAppWindow(ComponentNameMatcher.STATUS_BAR)
-    }
-
-    @Test
-    fun canDetectAboveAppWindowVisibility_isInvisible() {
-        val subject =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .containsAboveAppWindow(TestComponents.PIP_OVERLAY)
-                    .isNonAppWindowVisible(TestComponents.PIP_OVERLAY)
-            }
-        assertFailureFact(failure, "Is Invisible").contains("pip-dismiss-overlay")
-
-        failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .containsAboveAppWindow(ComponentNameMatcher.NAV_BAR)
-                    .isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
-            }
-        assertFailureFact(failure, "Is Visible").contains("NavigationBar")
-    }
-
-    @Test
-    fun canDetectWindowCoversAtLeastRegion_exactSize() {
-        val entry =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-
-        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversAtLeast(statusBarRegion)
-        entry.visibleRegion(TestComponents.LAUNCHER).coversAtLeast(displayBounds)
-    }
-
-    @Test
-    fun canDetectWindowCoversAtLeastRegion_smallerRegion() {
-        val entry =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        entry
-            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversAtLeast(Region.from(0, 0, 100, 100))
-        entry.visibleRegion(TestComponents.LAUNCHER).coversAtLeast(Region.from(0, 0, 100, 100))
-    }
-
-    @Test
-    fun canDetectWindowCoversAtLeastRegion_largerRegion() {
-        val subject =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-                    .coversAtLeast(Region.from(0, 0, 1441, 171))
-            }
-        assertFailureFact(failure, "Uncovered region").contains("SkRegion((1440,0,1441,171))")
-
-        failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(TestComponents.LAUNCHER)
-                    .coversAtLeast(Region.from(0, 0, 1440, 2961))
-            }
-        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,2960,1440,2961))")
-    }
-
-    @Test
-    fun canDetectWindowCoversExactlyRegion_exactSize() {
-        val entry =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-
-        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(statusBarRegion)
-        entry.visibleRegion(TestComponents.LAUNCHER).coversExactly(displayBounds)
-    }
-
-    @Test
-    fun canDetectWindowCoversExactlyRegion_smallerRegion() {
-        val subject =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-                    .coversAtMost(Region.from(0, 0, 100, 100))
-            }
-        assertFailureFact(failure, "Out-of-bounds region")
-            .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
-
-        failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(TestComponents.LAUNCHER)
-                    .coversAtMost(Region.from(0, 0, 100, 100))
-            }
-        assertFailureFact(failure, "Out-of-bounds region")
-            .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
-    }
-
-    @Test
-    fun canDetectWindowCoversExactlyRegion_largerRegion() {
-        val subject =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-                    .coversAtLeast(Region.from(0, 0, 1441, 171))
-            }
-        assertFailureFact(failure, "Uncovered region").contains("SkRegion((1440,0,1441,171))")
-
-        failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(TestComponents.LAUNCHER)
-                    .coversAtLeast(Region.from(0, 0, 1440, 2961))
-            }
-        assertFailureFact(failure, "Uncovered region").contains("SkRegion((0,2960,1440,2961))")
-    }
-
-    @Test
-    fun canDetectWindowCoversAtMostRegion_extactSize() {
-        val entry =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        entry.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversAtMost(statusBarRegion)
-        entry.visibleRegion(TestComponents.LAUNCHER).coversAtMost(displayBounds)
-    }
-
-    @Test
-    fun canDetectWindowCoversAtMostRegion_smallerRegion() {
-        val subject =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-        var failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-                    .coversAtMost(Region.from(0, 0, 100, 100))
-            }
-        assertFailureFact(failure, "Out-of-bounds region")
-            .contains("SkRegion((100,0,1440,100)(0,100,1440,171))")
-
-        failure =
-            assertThrows<FlickerSubjectException> {
-                subject
-                    .visibleRegion(TestComponents.LAUNCHER)
-                    .coversAtMost(Region.from(0, 0, 100, 100))
-            }
-        assertFailureFact(failure, "Out-of-bounds region")
-            .contains("SkRegion((100,0,1440,100)(0,100,1440,2960))")
-    }
-
-    @Test
-    fun canDetectWindowCoversAtMostRegion_largerRegion() {
-        val entry =
-            WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-
-        entry
-            .visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversAtMost(Region.from(0, 0, 1441, 171))
-        entry.visibleRegion(TestComponents.LAUNCHER).coversAtMost(Region.from(0, 0, 1440, 2961))
-    }
-
-    @Test
-    fun canDetectBelowAppWindowVisibility() {
-        WindowManagerTraceSubject(trace)
-            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-            .containsNonAppWindow(TestComponents.WALLPAPER)
-    }
-
-    @Test
-    fun canDetectAppWindowVisibility() {
-        WindowManagerTraceSubject(trace)
-            .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-            .containsAppWindow(TestComponents.LAUNCHER)
-
-        WindowManagerTraceSubject(trace)
-            .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
-            .containsAppWindow(TestComponents.CHROME_SPLASH_SCREEN)
-    }
-
-    @Test
-    fun canDetectAppWindowVisibilitySubject() {
-        val trace =
-            readWmTraceFromFile("wm_trace_launcher_visible_background.pb", legacyTrace = true)
-        val firstEntry = WindowManagerTraceSubject(trace).first()
-        val appWindowNames = firstEntry.wmState.appWindows.map { it.name }
-        val expectedAppWindowName =
-            "com.android.server.wm.flicker.testapp/" +
-                "com.android.server.wm.flicker.testapp.SimpleActivity"
-        firstEntry.check { "has1AppWindow" }.that(appWindowNames.size).isEqual(3)
-        firstEntry
-            .check { "App window names contain $expectedAppWindowName" }
-            .that(appWindowNames)
-            .contains(expectedAppWindowName)
-    }
-
-    @Test
-    fun canDetectLauncherVisibility() {
-        val trace =
-            readWmTraceFromFile("wm_trace_launcher_visible_background.pb", legacyTrace = true)
-        val subject = WindowManagerTraceSubject(trace)
-        val firstTrace = subject.first()
-        firstTrace.isAppWindowInvisible(TestComponents.LAUNCHER)
-
-        // in the trace there are 2 launcher windows, a visible (usually the main launcher) and
-        // an invisible one (the -1 window, for the swipe back on home screen action.
-        // in flicker, the launcher is considered visible is any of them is visible
-        subject.last().isAppWindowVisible(TestComponents.LAUNCHER)
-
-        subject
-            .isAppWindowNotOnTop(TestComponents.LAUNCHER)
-            .isAppWindowInvisible(TestComponents.LAUNCHER)
-            .then()
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .forAllEntries()
-    }
-
-    @Test
-    fun canFailWithReasonForVisibilityChecks_windowNotFound() {
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerTraceSubject(trace)
-                    .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-                    .containsNonAppWindow(TestComponents.IMAGINARY)
-            }
-        Truth.assertThat(failure).hasMessageThat().contains(TestComponents.IMAGINARY.packageName)
-    }
-
-    @Test
-    fun canFailWithReasonForVisibilityChecks_windowNotVisible() {
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerTraceSubject(trace)
-                    .getEntryByElapsedTimestamp(traceFirstFrameTimestamp)
-                    .containsNonAppWindow(ComponentNameMatcher.IME)
-                    .isNonAppWindowVisible(ComponentNameMatcher.IME)
-            }
-        assertFailureFact(failure, "Is Invisible").contains(ComponentNameMatcher.IME.packageName)
-    }
-
-    @Test
-    fun canDetectAppZOrder() {
-        WindowManagerTraceSubject(trace)
-            .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
-            .containsAppWindow(TestComponents.LAUNCHER)
-            .isAppWindowVisible(TestComponents.LAUNCHER)
-            .isAboveWindow(TestComponents.CHROME_SPLASH_SCREEN, TestComponents.LAUNCHER)
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-    }
-
-    @Test
-    fun canFailWithReasonForZOrderChecks_windowNotOnTop() {
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerTraceSubject(trace)
-                    .getEntryByElapsedTimestamp(traceFirstChromeFlashScreenTimestamp)
-                    .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
-            }
-        assertFailureFact(failure, "Found").contains(TestComponents.LAUNCHER.packageName)
-    }
-
-    @Test
-    fun canDetectActivityVisibility() {
-        val trace = readWmTraceFromFile("wm_trace_split_screen.pb", legacyTrace = true)
-        val lastEntry = WindowManagerTraceSubject(trace).last()
-        lastEntry.isAppWindowVisible(TestComponents.SHELL_SPLIT_SCREEN_PRIMARY)
-        lastEntry.isAppWindowVisible(TestComponents.SHELL_SPLIT_SCREEN_SECONDARY)
-    }
-
-    @Test
-    fun canHandleNoSubjects() {
-        val emptyRootContainer =
-            RootWindowContainer(
-                WindowContainer(
-                    title = "root",
-                    token = "",
-                    orientation = 0,
-                    layerId = 0,
-                    _isVisible = true,
-                    children = emptyArray(),
-                    configurationContainer = ConfigurationContainer(null, null, null),
-                    computedZ = 0
-                )
-            )
-        val noWindowsState =
-            WindowManagerState(
-                elapsedTimestamp = 0,
-                clockTimestamp = null,
-                where = "",
-                policy = null,
-                focusedApp = "",
-                focusedDisplayId = 0,
-                _focusedWindow = "",
-                inputMethodWindowAppToken = "",
-                isHomeRecentsComponent = false,
-                isDisplayFrozen = false,
-                _pendingActivities = emptyArray(),
-                root = emptyRootContainer,
-                keyguardControllerState =
-                    KeyguardControllerState.from(
-                        isAodShowing = false,
-                        isKeyguardShowing = false,
-                        keyguardOccludedStates = mapOf()
-                    )
-            )
-
-        val mockComponent = ComponentNameMatcher("", "Mock")
-
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerStateSubject(noWindowsState).isAppWindowOnTop(mockComponent)
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("No visible app windows found")
-    }
-
-    @Test
-    fun canDetectNoVisibleAppWindows() {
-        val trace = readWmTraceFromFile("wm_trace_unlock.pb", legacyTrace = true)
-        val firstEntry = WindowManagerTraceSubject(trace).first()
-        firstEntry.hasNoVisibleAppWindow()
-    }
-
-    @Test
-    fun canDetectHasVisibleAppWindows() {
-        val trace = readWmTraceFromFile("wm_trace_unlock.pb", legacyTrace = true)
-        val lastEntry = WindowManagerTraceSubject(trace).last()
-        val failure = assertThrows<FlickerSubjectException> { lastEntry.hasNoVisibleAppWindow() }
-        Truth.assertThat(failure).hasMessageThat().contains("Found visible windows")
-    }
-
-    @Test
-    fun canDetectTaskFragment() {
-        // Verify if parser can read a dump file with 2 TaskFragments showed side-by-side.
-        val trace = readWmTraceFromDumpFile("wm_trace_taskfragment.winscope")
-        // There's only one entry in dump file.
-        val entry = WindowManagerTraceSubject(trace).first()
-        // Verify there's exact 2 TaskFragments in window hierarchy.
-        Truth.assertThat(entry.wmState.taskFragments.size).isEqualTo(2)
-    }
-
-    @Test
-    fun canDetectIsHomeActivityVisibleTablet() {
-        val trace = readWmTraceFromDumpFile("tablet/wm_dump_home_screen.winscope")
-        // There's only one entry in dump file.
-        val entry = WindowManagerTraceSubject(trace).first()
-        // Verify that the device is in home screen
-        Truth.assertThat(entry.wmState.isHomeActivityVisible).isTrue()
-        // Verify that the subject is in home screen
-        entry.isHomeActivityVisible()
-    }
-
-    @Test
-    fun canDetectTaskBarIsVisible() {
-        val trace = readWmTraceFromDumpFile("tablet/wm_dump_home_screen.winscope")
-        // There's only one entry in dump file.
-        val entry = WindowManagerTraceSubject(trace).first()
-        // Verify that the taskbar is visible
-        entry.isNonAppWindowVisible(ComponentNameMatcher.TASK_BAR)
-    }
-
-    @Test
-    fun canDetectWindowVisibilityWhen2WindowsHaveSameName() {
-        val trace =
-            readWmTraceFromFile("wm_trace_2activities_same_name.winscope", legacyTrace = true)
-        val componentMatcher =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.NotificationActivity"
-            )
-        WindowManagerTraceSubject(trace)
-            .isAppWindowInvisible(componentMatcher)
-            .then()
-            .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-            .then()
-            .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-            .then()
-            .isAppWindowVisible(componentMatcher)
-            .forElapsedTimeRange(394872035003110L, 394874232110818L)
-    }
-
-    @Test
-    fun canDetectInvisibleWindowBecauseActivityIsInvisible() {
-        val entry = WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(9215551505798L)
-        entry.isAppWindowInvisible(TestComponents.CHROME_SPLASH_SCREEN)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateTest.kt
deleted file mode 100644
index b88613c..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerStateTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [WindowManagerState] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerStateTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerStateTest {
-
-    private val emptyRootContainer =
-        RootWindowContainer(
-            WindowContainer(
-                title = "root",
-                token = "",
-                orientation = 0,
-                layerId = 0,
-                _isVisible = true,
-                children = emptyArray(),
-                configurationContainer = ConfigurationContainer(null, null, null),
-                computedZ = 0
-            )
-        )
-
-    @Test
-    fun usesRealTimestampWhenAvailableAndFallsbackOnElapsedTimestamp() {
-        var entry =
-            WindowManagerState(
-                elapsedTimestamp = 100,
-                clockTimestamp = 600,
-                where = "",
-                policy = null,
-                focusedApp = "",
-                focusedDisplayId = 0,
-                _focusedWindow = "",
-                inputMethodWindowAppToken = "",
-                isHomeRecentsComponent = false,
-                isDisplayFrozen = false,
-                _pendingActivities = emptyArray(),
-                root = emptyRootContainer,
-                keyguardControllerState =
-                    KeyguardControllerState.from(
-                        isAodShowing = false,
-                        isKeyguardShowing = false,
-                        keyguardOccludedStates = mapOf()
-                    )
-            )
-        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
-
-        entry =
-            WindowManagerState(
-                elapsedTimestamp = 100,
-                clockTimestamp = null,
-                where = "",
-                policy = null,
-                focusedApp = "",
-                focusedDisplayId = 0,
-                _focusedWindow = "",
-                inputMethodWindowAppToken = "",
-                isHomeRecentsComponent = false,
-                isDisplayFrozen = false,
-                _pendingActivities = emptyArray(),
-                root = emptyRootContainer,
-                keyguardControllerState =
-                    KeyguardControllerState.from(
-                        isAodShowing = false,
-                        isKeyguardShowing = false,
-                        keyguardOccludedStates = mapOf()
-                    )
-            )
-        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.unixNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceEntryBuilderTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceEntryBuilderTest.kt
deleted file mode 100644
index f9924da..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceEntryBuilderTest.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTraceEntryBuilder
-import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
-import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
-import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [WindowManagerTraceEntryBuilder] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerTraceEntryBuilderTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerTraceEntryBuilderTest {
-
-    private val emptyRootContainer =
-        RootWindowContainer(
-            WindowContainer(
-                title = "root",
-                token = "",
-                orientation = 0,
-                layerId = 0,
-                _isVisible = true,
-                children = emptyArray(),
-                configurationContainer = ConfigurationContainer(null, null, null),
-                computedZ = 0
-            )
-        )
-
-    @Test
-    fun createsEntryWithCorrectClockTime() {
-        val builder =
-            WindowManagerTraceEntryBuilder(
-                _elapsedTimestamp = "100",
-                where = "",
-                policy = null,
-                focusedApp = "",
-                focusedDisplayId = 0,
-                focusedWindow = "",
-                inputMethodWindowAppToken = "",
-                isHomeRecentsComponent = false,
-                isDisplayFrozen = false,
-                pendingActivities = emptyArray(),
-                root = emptyRootContainer,
-                keyguardControllerState =
-                    KeyguardControllerState.from(
-                        isAodShowing = false,
-                        isKeyguardShowing = false,
-                        keyguardOccludedStates = mapOf()
-                    ),
-                realToElapsedTimeOffsetNs = "500"
-            )
-        val entry = builder.build()
-        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
-        Truth.assertThat(entry.clockTimestamp).isEqualTo(600)
-
-        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().systemUptimeNanos)
-        Truth.assertThat(entry.timestamp.unixNanos).isEqualTo(600)
-    }
-
-    @Test
-    fun supportsMissingRealToElapsedTimeOffsetNs() {
-        val builder =
-            WindowManagerTraceEntryBuilder(
-                _elapsedTimestamp = "100",
-                where = "",
-                policy = null,
-                focusedApp = "",
-                focusedDisplayId = 0,
-                focusedWindow = "",
-                inputMethodWindowAppToken = "",
-                isHomeRecentsComponent = false,
-                isDisplayFrozen = false,
-                pendingActivities = emptyArray(),
-                root = emptyRootContainer,
-                keyguardControllerState =
-                    KeyguardControllerState.from(
-                        isAodShowing = false,
-                        isKeyguardShowing = false,
-                        keyguardOccludedStates = mapOf()
-                    )
-            )
-        val entry = builder.build()
-        Truth.assertThat(entry.elapsedTimestamp).isEqualTo(100)
-        Truth.assertThat(entry.clockTimestamp).isEqualTo(null)
-
-        Truth.assertThat(entry.timestamp.elapsedNanos).isEqualTo(100)
-        Truth.assertThat(entry.timestamp.systemUptimeNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().systemUptimeNanos)
-        Truth.assertThat(entry.timestamp.unixNanos)
-            .isEqualTo(CrossPlatform.timestamp.empty().unixNanos)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt
deleted file mode 100644
index 3c18017..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceSubjectTest.kt
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.assertThatErrorContainsDebugInfo
-import com.android.server.wm.flicker.assertThrows
-import com.android.server.wm.flicker.readWmTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
-import com.android.server.wm.traces.common.subjects.FlickerSubjectException
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-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 [WindowManagerTraceSubject] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerTraceSubjectTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerTraceSubjectTest {
-    private val chromeTrace
-        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
-    private val imeTrace
-        get() = readWmTraceFromFile("wm_trace_ime.pb", legacyTrace = true)
-
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun testVisibleAppWindowForRange() {
-        WindowManagerTraceSubject(chromeTrace)
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .forElapsedTimeRange(9213763541297L, 9215536878453L)
-
-        WindowManagerTraceSubject(chromeTrace)
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .isAppWindowInvisible(TestComponents.CHROME_SPLASH_SCREEN)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
-            .isAppWindowInvisible(TestComponents.LAUNCHER)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
-            .isAppWindowInvisible(TestComponents.LAUNCHER)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .forElapsedTimeRange(9215551505798L, 9216093628925L)
-    }
-
-    @Test
-    fun testCanTransitionInAppWindow() {
-        WindowManagerTraceSubject(chromeTrace)
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanDetectTransitionWithOptionalValue() {
-        val trace = readWmTraceFromFile("wm_trace_open_from_overview.pb", legacyTrace = true)
-        val subject = WindowManagerTraceSubject(trace)
-        subject
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .then()
-            .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
-    }
-
-    @Test
-    fun testCanTransitionInAppWindow_withOptional() {
-        WindowManagerTraceSubject(chromeTrace)
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_SPLASH_SCREEN)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .then()
-            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
-            .isAboveAppWindowVisible(TestComponents.SCREEN_DECOR_OVERLAY)
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanInspectBeginning() {
-        WindowManagerTraceSubject(chromeTrace)
-            .first()
-            .isAppWindowOnTop(TestComponents.LAUNCHER)
-            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
-    }
-
-    @Test
-    fun testCanInspectAppWindowOnTop() {
-        WindowManagerTraceSubject(chromeTrace).first().isAppWindowOnTop(TestComponents.LAUNCHER)
-
-        val failure =
-            assertThrows<FlickerSubjectException> {
-                WindowManagerTraceSubject(chromeTrace)
-                    .first()
-                    .isAppWindowOnTop(TestComponents.IMAGINARY)
-                    .fail("Could not detect the top app window")
-            }
-        Truth.assertThat(failure).hasMessageThat().contains("ImaginaryWindow")
-    }
-
-    @Test
-    fun testCanInspectEnd() {
-        WindowManagerTraceSubject(chromeTrace)
-            .last()
-            .isAppWindowOnTop(TestComponents.CHROME_FIRST_RUN)
-            .containsAboveAppWindow(TestComponents.SCREEN_DECOR_OVERLAY)
-    }
-
-    @Test
-    fun testCanTransitionNonAppWindow() {
-        WindowManagerTraceSubject(imeTrace)
-            .skipUntilFirstAssertion()
-            .isNonAppWindowInvisible(ComponentNameMatcher.IME)
-            .then()
-            .isNonAppWindowVisible(ComponentNameMatcher.IME)
-            .forAllEntries()
-    }
-
-    @Test(expected = AssertionError::class)
-    fun testCanDetectOverlappingWindows() {
-        WindowManagerTraceSubject(imeTrace)
-            .doNotOverlap(
-                ComponentNameMatcher.IME,
-                ComponentNameMatcher.NAV_BAR,
-                TestComponents.IME_ACTIVITY
-            )
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanTransitionAboveAppWindow() {
-        WindowManagerTraceSubject(imeTrace)
-            .skipUntilFirstAssertion()
-            .isAboveAppWindowInvisible(ComponentNameMatcher.IME)
-            .then()
-            .isAboveAppWindowVisible(ComponentNameMatcher.IME)
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanTransitionBelowAppWindow() {
-        val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb", legacyTrace = true)
-        WindowManagerTraceSubject(trace)
-            .skipUntilFirstAssertion()
-            .isBelowAppWindowVisible(TestComponents.WALLPAPER)
-            .then()
-            .isBelowAppWindowInvisible(TestComponents.WALLPAPER)
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanDetectVisibleWindowsMoreThanOneConsecutiveEntry() {
-        val trace = readWmTraceFromFile("wm_trace_valid_visible_windows.pb", legacyTrace = true)
-        WindowManagerTraceSubject(trace)
-            .visibleWindowsShownMoreThanOneConsecutiveEntry()
-            .forAllEntries()
-    }
-
-    @Test
-    fun testCanAssertWindowStateSequence() {
-        val componentMatcher =
-            ComponentNameMatcher.unflattenFromString(
-                "com.android.chrome/org.chromium.chrome.browser.firstrun.FirstRunActivity"
-            )
-        val windowStates = WindowManagerTraceSubject(chromeTrace).windowStates(componentMatcher)
-
-        val visibilityChange =
-            windowStates.zipWithNext { current, next ->
-                current.windowState?.isVisible != next.windowState?.isVisible
-            }
-
-        Truth.assertWithMessage("Visibility should have changed only 1x in the trace")
-            .that(visibilityChange.count { it })
-            .isEqualTo(1)
-    }
-
-    @Test
-    fun exceptionContainsDebugInfo() {
-        val error =
-            assertThrows<AssertionError> { WindowManagerTraceSubject(chromeTrace).isEmpty() }
-        assertThatErrorContainsDebugInfo(error, withBlameEntry = false)
-    }
-
-    @Test
-    fun testCanDetectSnapshotStartingWindow() {
-        val trace =
-            readWmTraceFromFile(
-                "quick_switch_to_app_killed_in_background_trace.pb",
-                legacyTrace = true
-            )
-        val app1 =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.ImeActivity"
-            )
-        val app2 =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.SimpleActivity"
-            )
-        WindowManagerTraceSubject(trace)
-            .isAppWindowVisible(app1)
-            .then()
-            .isAppSnapshotStartingWindowVisibleFor(app2, isOptional = true)
-            .then()
-            .isAppWindowVisible(app2)
-            .then()
-            .isAppSnapshotStartingWindowVisibleFor(app1, isOptional = true)
-            .then()
-            .isAppWindowVisible(app1)
-            .forAllEntries()
-    }
-
-    @Test
-    fun canDetectAppInvisibleSnapshotStartingWindowVisible() {
-        val trace =
-            readWmTraceFromFile(
-                "quick_switch_to_app_killed_in_background_trace.pb",
-                legacyTrace = true
-            )
-        val subject = WindowManagerTraceSubject(trace).getEntryByElapsedTimestamp(694827105830L)
-        val app =
-            ComponentNameMatcher(
-                "com.android.server.wm.flicker.testapp",
-                "com.android.server.wm.flicker.testapp.SimpleActivity"
-            )
-        subject.isAppWindowInvisible(app)
-        subject.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT)
-    }
-
-    @Test
-    fun canDetectAppVisibleTablet() {
-        val trace = readWmTraceFromFile("tablet/wm_trace_open_chrome.winscope", legacyTrace = true)
-        WindowManagerTraceSubject(trace).isAppWindowVisible(TestComponents.CHROME).forAllEntries()
-    }
-
-    @Test
-    fun canDetectAppOpenRecentsTablet() {
-        val trace = readWmTraceFromFile("tablet/wm_trace_open_recents.winscope", legacyTrace = true)
-        WindowManagerTraceSubject(trace).isRecentsActivityVisible().forAllEntries()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt
deleted file mode 100644
index 5e9660c..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowManagerTraceTest.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.readWmTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
-import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import java.lang.reflect.Modifier
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [WindowManagerTrace] tests. To run this test: `atest
- * FlickerLibTest:WindowManagerTraceTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class WindowManagerTraceTest {
-    private val trace
-        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
-
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun canDetectAppWindow() {
-        val appWindows =
-            trace
-                .getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9213763541297L))
-                .appWindows
-        assertWithMessage("Unable to detect app windows").that(appWindows.size).isEqualTo(2)
-    }
-
-    /**
-     * Access all public methods and invokes all public getters from the object to check that all
-     * lazy properties contain valid values
-     */
-    private fun <T> Class<T>.accessProperties(obj: Any) {
-        val propertyValues =
-            this.declaredFields
-                .filter { Modifier.isPublic(it.modifiers) }
-                .map { kotlin.runCatching { Pair(it.name, it.get(obj)) } }
-                .filter { it.isFailure }
-
-        assertWithMessage(
-                "The following properties could not be read: " + propertyValues.joinToString("\n")
-            )
-            .that(propertyValues)
-            .isEmpty()
-
-        val getterValues =
-            this.declaredMethods
-                .filter {
-                    Modifier.isPublic(it.modifiers) &&
-                        it.name.startsWith("get") &&
-                        it.parameterCount == 0
-                }
-                .map { kotlin.runCatching { Pair(it.name, it.invoke(obj)) } }
-                .filter { it.isFailure }
-
-        assertWithMessage(
-                "The following methods could not be invoked: " + getterValues.joinToString("\n")
-            )
-            .that(getterValues)
-            .isEmpty()
-
-        this.superclass?.accessProperties(obj)
-        if (obj is WindowContainer) {
-            obj.children.forEach { it::class.java.accessProperties(it) }
-        }
-    }
-
-    /**
-     * Tests if all properties of the flicker objects are accessible. This is necessary because most
-     * values are lazy initialized and only trigger errors when being accessed for the first time.
-     */
-    @Test
-    fun canAccessAllProperties() {
-        arrayOf("wm_trace_activity_transition.pb", "wm_trace_openchrome2.pb").forEach { traceName ->
-            val trace = readWmTraceFromFile(traceName, legacyTrace = true)
-            assertWithMessage("Unable to parse dump").that(trace.entries.size).isGreaterThan(1)
-
-            trace.entries.forEach { entry: WindowManagerState ->
-                entry::class.java.accessProperties(entry)
-                entry.displays.forEach { it::class.java.accessProperties(it) }
-            }
-        }
-    }
-
-    @Test
-    fun canDetectValidState() {
-        val entry =
-            trace.getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9213763541297))
-        assertWithMessage("${entry.timestamp}: ${entry.getIsIncompleteReason()}")
-            .that(entry.isIncomplete())
-            .isFalse()
-    }
-
-    @Test
-    fun canDetectInvalidState() {
-        val entry =
-            trace.getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = 9215511235586))
-        assertWithMessage("${entry.timestamp}: ${entry.getIsIncompleteReason()}")
-            .that(entry.isIncomplete())
-            .isTrue()
-
-        assertThat(entry.getIsIncompleteReason()).contains("No resumed activities found")
-    }
-
-    @Test
-    fun canSlice() {
-        val trace =
-            readWmTraceFromFile(
-                "wm_trace_openchrome2.pb",
-                from = 174686204723645,
-                to = 174686640998584,
-                legacyTrace = true
-            )
-
-        assertThat(trace).isNotEmpty()
-        assertThat(trace.entries.first().timestamp.elapsedNanos).isEqualTo(174686204723645)
-        assertThat(trace.entries.last().timestamp.elapsedNanos).isEqualTo(174686640998584)
-    }
-
-    @Test
-    fun canSliceWithWrongTimestamps() {
-        val trace =
-            readWmTraceFromFile(
-                "wm_trace_openchrome2.pb",
-                from = 9213763541297,
-                to = 9215895891561,
-                legacyTrace = true
-            )
-        assertThat(trace).isEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt b/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt
deleted file mode 100644
index 2d58b0f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/windowmanager/WindowStateSubjectTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 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.flicker.windowmanager
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.TestComponents
-import com.android.server.wm.flicker.readWmTraceFromFile
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.common.subjects.wm.WindowManagerTraceSubject
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-class WindowStateSubjectTest {
-    private val trace
-        get() = readWmTraceFromFile("wm_trace_openchrome.pb", legacyTrace = true)
-
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun exceptionContainsDebugInfoImaginary() {
-        val foundWindow =
-            WindowManagerTraceSubject(trace).first().windowState(TestComponents.IMAGINARY.className)
-        Truth.assertWithMessage("${TestComponents.IMAGINARY.className} is not found")
-            .that(foundWindow)
-            .isNull()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/MockTraceParser.kt b/libraries/flicker/test/src/com/android/server/wm/parser/MockTraceParser.kt
deleted file mode 100644
index 7d4563b..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/MockTraceParser.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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
-
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-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.AbstractTraceParser
-
-class MockTraceParser(private val data: WindowManagerTrace) :
-    AbstractTraceParser<
-        WindowManagerTrace, WindowManagerState, WindowManagerState, WindowManagerTrace>() {
-    override val traceName: String = "In memory trace"
-
-    override fun createTrace(entries: List<WindowManagerState>): WindowManagerTrace =
-        WindowManagerTrace(entries.toTypedArray())
-
-    override fun doDecodeByteArray(bytes: ByteArray): WindowManagerTrace = data
-    override fun doParseEntry(entry: WindowManagerState): WindowManagerState = entry
-    override fun getEntries(input: WindowManagerTrace): List<WindowManagerState> =
-        input.entries.toList()
-    override fun getTimestamp(entry: WindowManagerState): Timestamp =
-        CrossPlatform.timestamp.from(elapsedNanos = entry.elapsedTimestamp)
-    override fun onBeforeParse(input: WindowManagerTrace) {}
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/TraceParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/TraceParserTest.kt
deleted file mode 100644
index 0737d8f..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/TraceParserTest.kt
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.utils.MockWindowManagerTraceBuilder
-import com.android.server.wm.flicker.utils.MockWindowStateBuilder
-import com.android.server.wm.traces.common.CrossPlatform
-import com.android.server.wm.traces.common.Timestamp
-import com.android.server.wm.traces.parser.AbstractTraceParser
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [AbstractTraceParser] (for trace slicing) */
-class TraceParserTest {
-    @Test
-    fun canSliceWithAllBefore() {
-        testSliceUsingElapsedTimestamp(
-            CrossPlatform.timestamp.min().elapsedNanos,
-            mockTraceForSliceTests.first().timestamp.elapsedNanos - 1,
-            listOf<Long>()
-        )
-    }
-
-    @Test
-    fun canSliceWithAllAfter() {
-        val from = mockTraceForSliceTests.last().elapsedTimestamp + 5
-        val to = mockTraceForSliceTests.last().elapsedTimestamp + 20
-        val splitLayersTraceWithoutInitialEntry =
-            MockTraceParser(mockTraceForSliceTests)
-                .parse(
-                    mockTraceForSliceTests,
-                    CrossPlatform.timestamp.from(elapsedNanos = from),
-                    CrossPlatform.timestamp.from(elapsedNanos = to),
-                    addInitialEntry = false
-                )
-        Truth.assertThat(splitLayersTraceWithoutInitialEntry).isEmpty()
-
-        val splitLayersTraceWithInitialEntry =
-            MockTraceParser(mockTraceForSliceTests)
-                .parse(
-                    mockTraceForSliceTests,
-                    CrossPlatform.timestamp.from(elapsedNanos = from),
-                    CrossPlatform.timestamp.from(elapsedNanos = to),
-                    addInitialEntry = true
-                )
-        Truth.assertThat(splitLayersTraceWithInitialEntry).hasSize(1)
-        Truth.assertThat(splitLayersTraceWithInitialEntry.first().timestamp)
-            .isEqualTo(mockTraceForSliceTests.last().timestamp)
-    }
-
-    @Test
-    fun canSliceInMiddle() {
-        testSliceUsingElapsedTimestamp(15L, 25L, listOf(15L, 18L, 25L))
-    }
-
-    @Test
-    fun canSliceFromBeforeFirstEntryToMiddle() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp.elapsedNanos - 1,
-            27L,
-            listOf(5L, 8L, 15L, 18L, 25L, 27L)
-        )
-    }
-
-    @Test
-    fun canSliceFromMiddleToAfterLastEntry() {
-        testSliceUsingElapsedTimestamp(
-            18L,
-            mockTraceForSliceTests.last().timestamp.elapsedNanos + 5,
-            listOf(18L, 25L, 27L, 30L)
-        )
-    }
-
-    @Test
-    fun canSliceFromBeforeToAfterLastEntry() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp.elapsedNanos - 1,
-            mockTraceForSliceTests.last().timestamp.elapsedNanos + 1,
-            mockTraceForSliceTests.map { it.timestamp }
-        )
-    }
-
-    @Test
-    fun canSliceFromExactStartToAfterLastEntry() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp,
-            mockTraceForSliceTests.last().timestamp.elapsedNanos + 1,
-            mockTraceForSliceTests.map { it.timestamp }
-        )
-    }
-
-    @Test
-    fun canSliceFromExactStartToExactEnd() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp,
-            mockTraceForSliceTests.last().timestamp,
-            mockTraceForSliceTests.map { it.timestamp }
-        )
-    }
-
-    @Test
-    fun canSliceFromExactStartToMiddle() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp,
-            18L,
-            listOf(5L, 8L, 15L, 18L)
-        )
-    }
-
-    @Test
-    fun canSliceFromMiddleToExactEnd() {
-        testSliceUsingElapsedTimestamp(
-            18L,
-            mockTraceForSliceTests.last().timestamp,
-            listOf(18L, 25L, 27L, 30L)
-        )
-    }
-
-    @Test
-    fun canSliceFromBeforeToExactEnd() {
-        testSliceUsingElapsedTimestamp(
-            mockTraceForSliceTests.first().timestamp.elapsedNanos - 1,
-            mockTraceForSliceTests.last().timestamp,
-            mockTraceForSliceTests.map { it.timestamp }
-        )
-    }
-
-    @Test
-    fun canSliceSameStartAndEnd() {
-        testSliceUsingElapsedTimestamp(15L, 15L, listOf(15L))
-    }
-
-    @JvmName("testSliceUsingElapsedTimestamp1")
-    private fun testSliceUsingElapsedTimestamp(from: Long, to: Long, expected: List<Timestamp>) {
-        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
-    }
-
-    @JvmName("testSliceUsingElapsedTimestamp1")
-    private fun testSliceUsingElapsedTimestamp(
-        from: Timestamp?,
-        to: Long,
-        expected: List<Timestamp>
-    ) {
-        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
-    }
-
-    private fun testSliceUsingElapsedTimestamp(
-        from: Long,
-        to: Timestamp?,
-        expected: List<Timestamp>
-    ) {
-        return testSliceUsingElapsedTimestamp(from, to, expected.map { it.elapsedNanos })
-    }
-
-    @JvmName("testSliceUsingElapsedTimestamp1")
-    private fun testSliceUsingElapsedTimestamp(from: Long, to: Timestamp?, expected: List<Long>) {
-        return testSliceUsingElapsedTimestamp(
-            from,
-            to?.elapsedNanos ?: error("missing elapsed timestamp"),
-            expected
-        )
-    }
-
-    @JvmName("testSliceUsingElapsedTimestamp2")
-    private fun testSliceUsingElapsedTimestamp(
-        from: Timestamp?,
-        to: Timestamp?,
-        expected: List<Timestamp>
-    ) {
-        return testSliceUsingElapsedTimestamp(
-            from?.elapsedNanos ?: error("missing elapsed timestamp"),
-            to?.elapsedNanos ?: error("missing elapsed timestamp"),
-            expected.map { it.elapsedNanos }
-        )
-    }
-
-    private fun testSliceUsingElapsedTimestamp(from: Timestamp?, to: Long, expected: List<Long>) {
-        return testSliceUsingElapsedTimestamp(
-            from?.elapsedNanos ?: error("missing elapsed timestamp"),
-            to,
-            expected
-        )
-    }
-
-    private fun testSliceUsingElapsedTimestamp(from: Long, to: Long, expected: List<Long>) {
-        require(from <= to) { "`from` not before `to`" }
-        val fromBefore = from < mockTraceForSliceTests.first().timestamp.elapsedNanos
-        val fromAfter = from < mockTraceForSliceTests.first().timestamp.elapsedNanos
-
-        val toBefore = to < mockTraceForSliceTests.first().timestamp.elapsedNanos
-        val toAfter = mockTraceForSliceTests.last().timestamp.elapsedNanos < to
-
-        require(
-            fromBefore ||
-                fromAfter ||
-                mockTraceForSliceTests.map { it.timestamp.elapsedNanos }.contains(from)
-        ) { "`from` need to be in the trace or before or after all entries" }
-        require(
-            toBefore ||
-                toAfter ||
-                mockTraceForSliceTests.map { it.timestamp.elapsedNanos }.contains(to)
-        ) { "`to` need to be in the trace or before or after all entries" }
-
-        testSliceWithOutInitialEntry(from, to, expected)
-        if (!fromAfter) {
-            testSliceWithOutInitialEntry(from - 1, to, expected)
-            testSliceWithOutInitialEntry(from - 1, to + 1, expected)
-        }
-        if (!toBefore) {
-            testSliceWithOutInitialEntry(from, to + 1, expected)
-        }
-
-        testSliceWithInitialEntry(from, to, expected)
-        if (!fromBefore) {
-            if (from < to) {
-                testSliceWithInitialEntry(from + 1, to, expected)
-            }
-            testSliceWithInitialEntry(from + 1, to + 1, expected)
-        }
-        if (!toBefore) {
-            testSliceWithInitialEntry(from, to + 1, expected)
-        }
-    }
-
-    private fun testSliceWithOutInitialEntry(from: Long, to: Long, expected: List<Long>) {
-        val splitLayersTrace =
-            MockTraceParser(mockTraceForSliceTests)
-                .parse(
-                    mockTraceForSliceTests,
-                    CrossPlatform.timestamp.from(elapsedNanos = from),
-                    CrossPlatform.timestamp.from(elapsedNanos = to),
-                    addInitialEntry = false
-                )
-        Truth.assertThat(splitLayersTrace.map { it.timestamp.elapsedNanos }).isEqualTo(expected)
-    }
-
-    private fun testSliceWithInitialEntry(from: Long, to: Long, expected: List<Long>) {
-        val splitLayersTraceWithStartEntry =
-            MockTraceParser(mockTraceForSliceTests)
-                .parse(
-                    mockTraceForSliceTests,
-                    CrossPlatform.timestamp.from(elapsedNanos = from),
-                    CrossPlatform.timestamp.from(elapsedNanos = to),
-                    addInitialEntry = true
-                )
-        Truth.assertThat(splitLayersTraceWithStartEntry.map { it.timestamp.elapsedNanos })
-            .isEqualTo(expected)
-    }
-
-    companion object {
-        val mockTraceForSliceTests =
-            MockWindowManagerTraceBuilder(
-                    entries =
-                        mutableListOf(
-                            MockWindowStateBuilder(timestamp = 5),
-                            MockWindowStateBuilder(timestamp = 8),
-                            MockWindowStateBuilder(timestamp = 15),
-                            MockWindowStateBuilder(timestamp = 18),
-                            MockWindowStateBuilder(timestamp = 25),
-                            MockWindowStateBuilder(timestamp = 27),
-                            MockWindowStateBuilder(timestamp = 30),
-                        )
-                )
-                .build()
-
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/UiDeviceExtensionsTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/UiDeviceExtensionsTest.kt
deleted file mode 100644
index aa9bca1..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/UiDeviceExtensionsTest.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 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
-
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.NullableDeviceStateDump
-import com.android.server.wm.traces.parser.FLAG_STATE_DUMP_FLAG_LAYERS
-import com.android.server.wm.traces.parser.FLAG_STATE_DUMP_FLAG_WM
-import com.android.server.wm.traces.parser.WmStateDumpFlags
-import com.android.server.wm.traces.parser.getCurrentState
-import com.android.server.wm.traces.parser.getCurrentStateDumpNullable
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runners.MethodSorters
-
-/**
- * Contains [com.android.server.wm.traces.parser.Extensions] tests.
- *
- * To run this test: `atest FlickerLibTest:UiDeviceExtensionsTest`
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class UiDeviceExtensionsTest {
-    private fun getCurrState(
-        @WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS)
-    ): Pair<ByteArray, ByteArray> {
-        val instrumentation = InstrumentationRegistry.getInstrumentation()
-        return getCurrentState(instrumentation.uiAutomation, dumpFlags)
-    }
-
-    private fun getCurrStateDump(
-        @WmStateDumpFlags dumpFlags: Int = FLAG_STATE_DUMP_FLAG_WM.or(FLAG_STATE_DUMP_FLAG_LAYERS)
-    ): NullableDeviceStateDump {
-        val instrumentation = InstrumentationRegistry.getInstrumentation()
-        return getCurrentStateDumpNullable(
-            instrumentation.uiAutomation,
-            dumpFlags,
-            clearCacheAfterParsing = false
-        )
-    }
-
-    @Test
-    fun canFetchCurrentDeviceState() {
-        val currState = this.getCurrState()
-        Truth.assertThat(currState.first).isNotEmpty()
-        Truth.assertThat(currState.second).isNotEmpty()
-    }
-
-    @Test
-    fun canFetchCurrentDeviceStateOnlyWm() {
-        val currStateDump = this.getCurrState(FLAG_STATE_DUMP_FLAG_WM)
-        Truth.assertThat(currStateDump.first).isNotEmpty()
-        Truth.assertThat(currStateDump.second).isEmpty()
-        val currState = this.getCurrStateDump(FLAG_STATE_DUMP_FLAG_WM)
-        Truth.assertThat(currState.wmState).isNotNull()
-        Truth.assertThat(currState.layerState).isNull()
-    }
-
-    @Test
-    fun canFetchCurrentDeviceStateOnlyLayers() {
-        val currStateDump = this.getCurrState(FLAG_STATE_DUMP_FLAG_LAYERS)
-        Truth.assertThat(currStateDump.first).isEmpty()
-        Truth.assertThat(currStateDump.second).isNotEmpty()
-        val currState = this.getCurrStateDump(FLAG_STATE_DUMP_FLAG_LAYERS)
-        Truth.assertThat(currState.wmState).isNull()
-        Truth.assertThat(currState.layerState).isNotNull()
-    }
-
-    @Test
-    fun canParseCurrentDeviceState() {
-        val currState = this.getCurrStateDump()
-        Truth.assertThat(currState.wmState?.asTrace()?.entries ?: emptyArray()).asList().hasSize(1)
-        Truth.assertThat(currState.wmState?.asTrace()?.entries?.first()?.windowStates).isNotEmpty()
-        Truth.assertThat(currState.layerState?.asTrace()?.entries ?: emptyArray())
-            .asList()
-            .hasSize(1)
-        Truth.assertThat(currState.layerState?.asTrace()?.entries?.first()?.flattenedLayers)
-            .isNotEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/layers/LayerTraceParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/layers/LayerTraceParserTest.kt
deleted file mode 100644
index 7d1f539..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/layers/LayerTraceParserTest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.layers
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.readAsset
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.parser.layers.LayersTraceParser
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-class LayerTraceParserTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun canParseFromTrace() {
-        val trace =
-            LayersTraceParser(legacyTrace = true).parse(readAsset("layers_trace_occluded.pb"))
-        Truth.assertWithMessage("Trace").that(trace).isNotEmpty()
-        Truth.assertWithMessage("Trace contains entry")
-            .that(trace.entries.map { it.elapsedTimestamp })
-            .contains(1700382131522L)
-    }
-
-    @Test
-    fun canParseFromDumpWithDisplay() {
-        val trace =
-            LayersTraceParser(legacyTrace = true).parse(readAsset("layers_dump_with_display.pb"))
-        Truth.assertWithMessage("Dump").that(trace).isNotEmpty()
-        Truth.assertWithMessage("Dump contains display")
-            .that(trace.first().displays)
-            .asList()
-            .isNotEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerDumpParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerDumpParserTest.kt
deleted file mode 100644
index fc23e15..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerDumpParserTest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.readAsset
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.parser.FLAG_STATE_DUMP_FLAG_WM
-import com.android.server.wm.traces.parser.getCurrentState
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerDumpParser
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [WindowManagerDumpParser] */
-class WindowManagerDumpParserTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun canParseFromStoredDump() {
-        val trace = WindowManagerDumpParser().parse(readAsset("wm_trace_dump.pb"))
-        Truth.assertWithMessage("Unable to parse dump").that(trace).hasSize(1)
-    }
-
-    @Test
-    fun canParseFromNewDump() {
-        val data =
-            getCurrentState(
-                InstrumentationRegistry.getInstrumentation().uiAutomation,
-                FLAG_STATE_DUMP_FLAG_WM
-            )
-        val trace = WindowManagerDumpParser().parse(data.first)
-        Truth.assertWithMessage("Unable to parse dump").that(trace).hasSize(1)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerStateHelperTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerStateHelperTest.kt
deleted file mode 100644
index d50a7ee..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerStateHelperTest.kt
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * 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()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerTraceParserTest.kt b/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerTraceParserTest.kt
deleted file mode 100644
index c946753..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/parser/windowmanager/WindowManagerTraceParserTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.InitRule
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
-import com.android.server.wm.flicker.readAsset
-import com.android.server.wm.traces.common.Cache
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerTraceParser
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [WindowManagerTraceParser] */
-class WindowManagerTraceParserTest {
-    @Before
-    fun before() {
-        Cache.clear()
-    }
-
-    @Test
-    fun canParseAllEntriesFromStoredTrace() {
-        val trace =
-            WindowManagerTraceParser(legacyTrace = true)
-                .parse(readAsset("wm_trace_openchrome.pb"), clearCache = false)
-        val firstEntry = trace.entries[0]
-        Truth.assertThat(firstEntry.timestamp.elapsedNanos).isEqualTo(9213763541297L)
-        Truth.assertThat(firstEntry.windowStates.size).isEqualTo(10)
-        Truth.assertThat(firstEntry.visibleWindows.size).isEqualTo(5)
-        Truth.assertThat(trace.entries[trace.entries.size - 1].timestamp.elapsedNanos)
-            .isEqualTo(9216093628925L)
-    }
-
-    @Test
-    fun canParseAllEntriesFromNewTrace() {
-        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-        val data =
-            WindowManagerTraceMonitor().withTracing {
-                device.pressHome()
-                device.pressRecentApps()
-            }
-        val trace = WindowManagerTraceParser().parse(data, clearCache = false)
-        Truth.assertThat(trace.entries).asList().isNotEmpty()
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}
diff --git a/libraries/flicker/test/src/com/android/server/wm/traces/common/events/CujTraceTest.kt b/libraries/flicker/test/src/com/android/server/wm/traces/common/events/CujTraceTest.kt
deleted file mode 100644
index 45b850e..0000000
--- a/libraries/flicker/test/src/com/android/server/wm/traces/common/events/CujTraceTest.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.traces.common.events
-
-import com.android.server.wm.InitRule
-import com.android.server.wm.traces.common.CrossPlatform
-import com.google.common.truth.Truth
-import org.junit.ClassRule
-import org.junit.Test
-
-/** Tests for [CujTrace]. Run with `atest FlickerLibTest:CujTraceTest` */
-class CujTraceTest {
-    @Test
-    fun canCreateFromArrayOfCujEvents() {
-        val trace =
-            CujTrace.from(
-                arrayOf(
-                    createCujEvent(
-                        1,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_BEGIN_TAG
-                    ),
-                    createCujEvent(
-                        2,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_END_TAG
-                    ),
-                )
-            )
-
-        Truth.assertThat(trace.entries).hasLength(1)
-        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
-        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
-        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
-        Truth.assertThat(trace.entries[0].canceled).isFalse()
-    }
-
-    @Test
-    fun canCreateCanceledCujsFromArrayOfCujEvents() {
-        val trace =
-            CujTrace.from(
-                arrayOf(
-                    createCujEvent(
-                        1,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_BEGIN_TAG
-                    ),
-                    createCujEvent(
-                        2,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_CANCEL_TAG
-                    ),
-                )
-            )
-
-        Truth.assertThat(trace.entries).hasLength(1)
-        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
-        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
-        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
-        Truth.assertThat(trace.entries[0].canceled).isTrue()
-    }
-
-    @Test
-    fun canHandleIncompleteCujs() {
-        val trace =
-            CujTrace.from(
-                arrayOf(
-                    createCujEvent(
-                        1,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_CANCEL_TAG
-                    ),
-                    createCujEvent(
-                        2,
-                        CujType.CUJ_BIOMETRIC_PROMPT_TRANSITION,
-                        CujEvent.JANK_CUJ_END_TAG
-                    ),
-                    createCujEvent(
-                        3,
-                        CujType.CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
-                        CujEvent.JANK_CUJ_BEGIN_TAG
-                    ),
-                )
-            )
-
-        Truth.assertThat(trace.entries).hasLength(0)
-    }
-
-    @Test
-    fun canHandleOutOfOrderEntries() {
-        val trace =
-            CujTrace.from(
-                arrayOf(
-                    createCujEvent(
-                        2,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_END_TAG
-                    ),
-                    createCujEvent(
-                        1,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_BEGIN_TAG
-                    ),
-                )
-            )
-
-        Truth.assertThat(trace.entries).hasLength(1)
-        Truth.assertThat(trace.entries[0].cuj).isEqualTo(CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL)
-        Truth.assertThat(trace.entries[0].startTimestamp.unixNanos).isEqualTo(1)
-        Truth.assertThat(trace.entries[0].endTimestamp.unixNanos).isEqualTo(2)
-        Truth.assertThat(trace.entries[0].canceled).isFalse()
-    }
-
-    @Test
-    fun canHandleMissingStartAndEnds() {
-        val trace =
-            CujTrace.from(
-                arrayOf(
-                    createCujEvent(
-                        1,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_END_TAG
-                    ),
-                    createCujEvent(
-                        2,
-                        CujType.CUJ_LAUNCHER_ALL_APPS_SCROLL,
-                        CujEvent.JANK_CUJ_BEGIN_TAG
-                    ),
-                )
-            )
-
-        Truth.assertThat(trace.entries).hasLength(0)
-    }
-
-    private fun createCujEvent(timestamp: Long, cuj: CujType, tag: String): CujEvent {
-        return CujEvent(CrossPlatform.timestamp.from(unixNanos = timestamp), cuj, 0, "root", 0, tag)
-    }
-
-    companion object {
-        @ClassRule @JvmField val initRule = InitRule()
-    }
-}