Merge "Remove Paparazzi from androidx" into androidx-main
diff --git a/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle b/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle
deleted file mode 100644
index ece0f79..0000000
--- a/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-import androidx.build.paparazzi.AndroidXPaparazziImplPlugin
-
-buildscript {
-    dependencies {
-        classpath(project.files("${project.rootProject.ext["buildSrcOut"]}/private/build/libs/private.jar"))
-    }
-}
-
-apply plugin: AndroidXPaparazziImplPlugin
diff --git a/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt b/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt
deleted file mode 100644
index 090d9b6..0000000
--- a/buildSrc/plugins/src/main/kotlin/androidx/build/AndroidXPaparazziPlugin.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 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 androidx.build
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-/**
- * Configures screenshot testing using Paparazzi for AndroidX projects.
- *
- * The actual implementation is in AndroidXPaparazziImplPlugin.
- */
-class AndroidXPaparazziPlugin : Plugin<Project> {
-    override fun apply(project: Project) {
-        val supportRoot = project.getSupportRootFolder()
-        project.apply(
-            mapOf("from" to "$supportRoot/buildSrc/apply/applyAndroidXPaparazziImplPlugin.gradle")
-        )
-    }
-}
diff --git a/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties b/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties
deleted file mode 100644
index 872551c..0000000
--- a/buildSrc/plugins/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziPlugin.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright 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.
-#
-
-implementation-class=androidx.build.AndroidXPaparazziPlugin
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt
deleted file mode 100644
index 6c3b775..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/AndroidXPaparazziImplPlugin.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright 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 androidx.build.paparazzi
-
-import androidx.build.OperatingSystem
-import androidx.build.defaultAndroidConfig
-import androidx.build.getLibraryByName
-import androidx.build.getOperatingSystem
-import androidx.build.getSdkPath
-import androidx.build.getSupportRootFolder
-import com.android.build.gradle.BaseExtension
-import javax.inject.Inject
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE
-import org.gradle.api.artifacts.type.ArtifactTypeDefinition.JAR_TYPE
-import org.gradle.api.file.Directory
-import org.gradle.api.file.FileCollection
-import org.gradle.api.file.FileSystemOperations
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.Copy
-import org.gradle.api.tasks.PathSensitivity
-import org.gradle.api.tasks.testing.Test
-import org.gradle.kotlin.dsl.get
-import org.gradle.kotlin.dsl.register
-import org.gradle.kotlin.dsl.the
-import org.gradle.kotlin.dsl.withType
-import org.gradle.process.JavaForkOptions
-
-/** Configures screenshot testing using Paparazzi for AndroidX projects. */
-class AndroidXPaparazziImplPlugin
-@Inject
-constructor(private val fileSystemOperations: FileSystemOperations) : Plugin<Project> {
-    override fun apply(project: Project) {
-        val paparazziNative = project.createUnzippedPaparazziNativeDependency()
-        project.afterEvaluate { it.addTestUtilsDependency() }
-        project.tasks.register("updateGolden")
-        project.afterEvaluate {
-            // need to be inside of afterEvaluate because we read android.namespace
-            // ideally, we refactor to use a lazy API
-            project.tasks.withType<Test>().configureEach { it.configureTestTask(paparazziNative) }
-        }
-        project.tasks.withType<Test>().whenTaskAdded { project.registerUpdateGoldenTask(it) }
-    }
-
-    /**
-     * Add project's golden directory and the unzipped native Paparazzi location as task inputs, and
-     * set system properties for the test library to consume at runtime.
-     */
-    private fun Test.configureTestTask(paparazziNative: FileCollection) {
-        val compileSdkVersion = project.defaultAndroidConfig.compileSdk
-        val platformDirectory = project.getSdkPath().resolve("platforms/$compileSdkVersion")
-        val cachedGoldenRootDirectory = project.goldenRootDirectory
-        val cachedReportDirectory = reportDirectory
-        val android = project.the<BaseExtension>()
-        val packageName =
-            requireNotNull(android.namespace) { "android.namespace must be set for Paparazzi" }
-
-        // Attach unzipped Paparazzi native directory as a task input
-        inputs
-            .files(paparazziNative)
-            .withPathSensitivity(PathSensitivity.NONE)
-            .withPropertyName("paparazziNative")
-
-        // Attach golden directory to task inputs to invalidate tests when updating goldens
-        inputs
-            .dir(project.goldenDirectory)
-            .withPathSensitivity(PathSensitivity.RELATIVE)
-            .withPropertyName("goldenDirectory")
-
-        // Mark report directory as an output directory
-        outputs.dir(reportDirectory).withPropertyName("paparazziReportDir")
-
-        // Clean the contents of the report directory before each test run
-        doFirst {
-            fileSystemOperations.delete {
-                it.delete(cachedReportDirectory.get().asFile.listFiles())
-            }
-        }
-
-        // Set non-path system properties at configuration time, so that changes invalidate caching
-        prefixedSystemProperties(
-            "gradlePluginApplied" to "true",
-            "compileSdkVersion" to project.defaultAndroidConfig.targetSdk,
-            "resourcePackageNames" to packageName, // TODO: Transitive resource packages?
-            "modulePath" to project.modulePath,
-            "updateGoldenTask" to "${project.path}:${updateGoldenTaskName()}"
-        )
-
-        // Set the remaining system properties at execution time, after the snapshotting, so that
-        // the absolute paths don't affect caching
-        doFirst {
-            systemProperty("paparazzi.platform.data.root", paparazziNative.singleFile.canonicalPath)
-            prefixedSystemProperties(
-                "platformDir" to platformDirectory.canonicalPath,
-                "assetsDir" to ".", // TODO: Merged assets dirs? (needed for compose?)
-                "resDir" to ".", // TODO: Merged resource dirs? (needed for compose?)
-                "reportDir" to cachedReportDirectory.get().asFile.canonicalPath,
-                "goldenRootDir" to cachedGoldenRootDirectory.canonicalPath,
-            )
-        }
-    }
-
-    /** Register a copy task for moving new images to the golden directory. */
-    private fun Project.registerUpdateGoldenTask(testTask: Test) {
-        tasks.register<Copy>(testTask.updateGoldenTaskName()) {
-            dependsOn(testTask)
-
-            from(testTask.reportDirectory) {
-                include("**/*_actual.png")
-                into(goldenDirectory)
-                rename { it.removeSuffix("_actual.png") + "_paparazzi.png" }
-            }
-        }
-
-        tasks["updateGolden"].dependsOn(testTask.updateGoldenTaskName())
-    }
-
-    /** Derive updateGolden task name from a test task name. */
-    private fun Test.updateGoldenTaskName(): String {
-        return "updateGolden" + name.removePrefix("test").replaceFirstChar { it.titlecase() }
-    }
-
-    /**
-     * Configure [UnzipPaparazziNativeTransform] for the project, and add the platform-specific
-     * Paparazzi native layoutlib dependency, using the version in `libs.versions.toml`.
-     */
-    private fun Project.createUnzippedPaparazziNativeDependency(): FileCollection {
-        val platformSuffix =
-            when (val os = getOperatingSystem()) {
-                OperatingSystem.LINUX -> "LinuxX64"
-                OperatingSystem.MAC -> {
-                    val arch = System.getProperty("os.arch")
-                    if (arch.startsWith("x86", ignoreCase = true)) "MacOsX64" else "MacOsArm64"
-                }
-                else -> error("Unsupported operating system $os for Paparazzi")
-            }
-
-        dependencies.registerTransform(UnzipPaparazziNativeTransform::class.java) { spec ->
-            spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_TYPE)
-            spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, UNZIPPED_PAPARAZZI_NATIVE)
-        }
-
-        val configuration = configurations.create("paparazziNative")
-        configuration.isCanBeConsumed = false
-        configuration.dependencies.add(
-            dependencies.create(getLibraryByName("paparazziNative$platformSuffix"))
-        )
-
-        return configuration.incoming
-            .artifactView {
-                it.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, UNZIPPED_PAPARAZZI_NATIVE)
-            }
-            .files
-    }
-
-    /** The golden image directory for this project. */
-    private val Project.goldenDirectory
-        get() = goldenRootDirectory.resolve(modulePath)
-
-    /** The root of the golden image directory in a standard AndroidX checkout. */
-    private val Project.goldenRootDirectory
-        get() = getSupportRootFolder().resolve("../../golden")
-
-    /** Filesystem path for this module derived from Gradle project path. */
-    private val Project.modulePath
-        get() = path.replace(':', '/').trim('/')
-
-    /** Output directory for storing reports and images. */
-    private val Test.reportDirectory: Provider<Directory>
-        get() = project.layout.buildDirectory.dir("paparazzi/$name")
-
-    /** Add a testImplementation dependency on the wrapper test utils library. */
-    private fun Project.addTestUtilsDependency() {
-        configurations["testImplementation"]
-            .dependencies
-            .add(dependencies.create(project(TEST_UTILS_PROJECT)))
-    }
-
-    private companion object {
-        /** Package name of the test library, used to namespace system properties */
-        const val PACKAGE_NAME = "androidx.testutils.paparazzi"
-
-        /** Project path to the wrapper test utils project. */
-        const val TEST_UTILS_PROJECT = ":internal-testutils-paparazzi"
-
-        /** Artifact type attribute for unzipped Paparazzi layoutlib unzipped artifacts */
-        const val UNZIPPED_PAPARAZZI_NATIVE = "unzipped-paparazzi-native"
-
-        /** Set system properties with keys prefixed with [PACKAGE_NAME] */
-        fun JavaForkOptions.prefixedSystemProperties(vararg properties: Pair<String, Any>) {
-            properties.forEach { (name, value) -> systemProperty("$PACKAGE_NAME.$name", value) }
-        }
-    }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt b/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt
deleted file mode 100644
index 2d2c09a..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/paparazzi/UnzipPaparazziNativeTransform.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 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 androidx.build.paparazzi
-
-import java.util.zip.ZipInputStream
-import org.gradle.api.artifacts.transform.InputArtifact
-import org.gradle.api.artifacts.transform.TransformAction
-import org.gradle.api.artifacts.transform.TransformOutputs
-import org.gradle.api.artifacts.transform.TransformParameters.None
-import org.gradle.api.file.FileSystemLocation
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.PathSensitive
-import org.gradle.api.tasks.PathSensitivity
-import org.gradle.work.DisableCachingByDefault
-
-/**
- * Unzips one of Paparazzi's platform-specific layoutlib JAR artifacts so that Paparazzi can read
- * its contents at run time. These contain a native dynamic library and supporting resources
- * including ICU and fonts.
- */
-@DisableCachingByDefault(because = "Just an unzip task, faster to rerun locally")
-abstract class UnzipPaparazziNativeTransform : TransformAction<None> {
-    @get:PathSensitive(PathSensitivity.NAME_ONLY)
-    @get:InputArtifact
-    abstract val primaryInput: Provider<FileSystemLocation>
-
-    override fun transform(outputs: TransformOutputs) {
-        val inputFile = primaryInput.get().asFile
-        val outputDir = outputs.dir(inputFile.nameWithoutExtension).also { it.mkdirs() }
-
-        ZipInputStream(inputFile.inputStream().buffered()).use { zipInputStream ->
-            while (true) {
-                val entry = zipInputStream.nextEntry ?: break
-                val outputFile = outputDir.resolve(entry.name)
-
-                if (entry.isDirectory) {
-                    outputFile.mkdir()
-                } else {
-                    // This works because ZipInputStream resizes itself to the contents of the
-                    // last-returned entry
-                    outputFile.outputStream().buffered().use { zipInputStream.copyTo(it) }
-                }
-            }
-        }
-    }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt b/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
index b6c9910..877109a 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/transform/ConfigureAarAsJar.kt
@@ -63,8 +63,6 @@
         .dependencies
         .add(project.dependencies.create(aarAsJar))
 
-    // Added to allow the :external:paparazzi:paparazzi build to select the correct jar (not get
-    // confused by the mpp jars) when the mpp builds are enabled
     project.configurations
         .getByName(testAarsAsJars.name)
         .attributes
diff --git a/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties b/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties
deleted file mode 100644
index 0ea8b07..0000000
--- a/buildSrc/private/src/main/resources/META-INF/gradle-plugins/AndroidXPaparazziImplPlugin.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright 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.
-#
-
-implementation-class=androidx.build.paparazzi.AndroidXPaparazziImplPlugin
diff --git a/buildSrc/settingsScripts/project-dependency-graph.groovy b/buildSrc/settingsScripts/project-dependency-graph.groovy
index d74e0d8..34e95d3 100644
--- a/buildSrc/settingsScripts/project-dependency-graph.groovy
+++ b/buildSrc/settingsScripts/project-dependency-graph.groovy
@@ -239,10 +239,6 @@
                     links.add(":compose:compiler:compiler")
                     links.add(":compose:lint:internal-lint-checks")
                 }
-                if (paparazziPlugin.matcher(line).find()) {
-                    links.add(":test:screenshot:screenshot-proto")
-                    links.add(":internal-testutils-paparazzi")
-                }
                 if (iconGenerator.matcher(line).find()) {
                     links.add(":compose:material:material:icons:generator")
                 }
@@ -267,7 +263,6 @@
     private static Pattern multilineProjectReference = Pattern.compile("project\\(\$")
     private static Pattern inspection = Pattern.compile("packageInspector\\(project, \"(.*)\"\\)")
     private static Pattern composePlugin = Pattern.compile("id\\(\"AndroidXComposePlugin\"\\)")
-    private static Pattern paparazziPlugin = Pattern.compile("id\\(\"AndroidXPaparazziPlugin\"\\)")
     private static Pattern iconGenerator = Pattern.compile("IconGenerationTask\\.register")
 }
 
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 29b6d1b..ab1f2c9 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 
@@ -122,8 +121,8 @@
                 implementation(libs.junit)
                 implementation(libs.truth)
                 implementation(libs.kotlinReflect)
-                implementation(libs.mockitoCore)
-                implementation(libs.mockitoKotlin)
+                implementation(libs.mockitoCore4)
+                implementation(libs.mockitoKotlin4)
                 implementation(project(":constraintlayout:constraintlayout-compose"))
             }
         }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt
index 968f6ec..700bbca 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ToCharArray.android.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.text.input.internal
 
-import android.text.TextUtils
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text.input.TextFieldCharSequence
 import androidx.compose.foundation.text.input.toCharArray
@@ -31,6 +30,9 @@
     if (this is TextFieldCharSequence) {
         toCharArray(destination, destinationOffset, startIndex, endIndex)
     } else {
-        TextUtils.getChars(this, startIndex, endIndex, destination, destinationOffset)
+        var dstIndex = destinationOffset
+        for (srcIndex in startIndex until endIndex) {
+            destination[dstIndex++] = this[srcIndex]
+        }
     }
 }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
deleted file mode 100644
index c65c711..0000000
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 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 androidx.compose.foundation.text
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.constraintlayout.compose.ConstraintLayout
-import androidx.constraintlayout.compose.Dimension
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class BasicTextPaparazziTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun colorChangingState_changesColor() {
-        paparazzi.snapshot {
-            var color = remember { mutableStateOf(Color.Red) }
-            BasicText(
-                "ABCD",
-                color = { color.value }
-            )
-            SideEffect {
-                color.value = Color.Yellow
-            }
-        }
-    }
-
-    @Test
-    fun colorChangingState_changesColor_annotatedString() {
-        paparazzi.snapshot {
-            var color = remember { mutableStateOf(Color.Red) }
-            BasicText(
-                AnnotatedString("ABCD"),
-                color = { color.value }
-            )
-            SideEffect {
-                color.value = Color.Yellow
-            }
-        }
-    }
-
-    @Test
-    fun overflowEllipsis_doesEllipsis_whenInPreferredWrapContent() {
-        paparazzi.snapshot {
-            // b/275369323
-            ConstraintLayout(modifier = Modifier
-                .width(100.dp)
-                .background(Color.White)
-                .padding(8.dp)) {
-                val (thumbnail, textBox, actionButton) = createRefs()
-                Box(modifier = Modifier
-                    .background(Color.Green)
-                    .constrainAs(thumbnail) {
-                        top.linkTo(parent.top)
-                        start.linkTo(parent.start)
-                        bottom.linkTo(parent.bottom)
-                    }
-                    .size(28.dp)
-                )
-                // Text region
-                Column(
-                    modifier = Modifier
-                        .constrainAs(textBox) {
-                            top.linkTo(parent.top)
-                            bottom.linkTo(parent.bottom)
-                            start.linkTo(thumbnail.end)
-                            end.linkTo(actionButton.start)
-                            width = Dimension.preferredWrapContent
-                        },
-                ) {
-                    BasicText(
-                        text = "ASome very long text that is sure to clip in this layout",
-                        maxLines = 1,
-                        overflow = TextOverflow.Ellipsis
-                    )
-                }
-                Box(modifier = Modifier
-                    .constrainAs(actionButton) {
-                        top.linkTo(parent.top)
-                        bottom.linkTo(parent.bottom)
-                        end.linkTo(parent.end)
-                    }
-                    .background(Color.Blue.copy(alpha = 0.5f))
-                    .size(28.dp)
-                )
-            }
-        }
-    }
-
-    @Test
-    fun RtlAppliedCorrectly_inConstraintLayout_withWrapContentText() {
-        // b/275369323
-        paparazzi.snapshot {
-            CompositionLocalProvider(
-                LocalLayoutDirection provides LayoutDirection.Rtl
-            ) {
-                ConstraintLayout(
-                    Modifier
-                        .fillMaxWidth()
-                        .background(Color.Green)) {
-                    val (title, progressBar, expander) = createRefs()
-                    BasicText(
-                        text = "Locale-aware Text",
-                        modifier = Modifier
-                            .constrainAs(title) {
-                                top.linkTo(parent.top)
-                                start.linkTo(parent.start)
-                                end.linkTo(expander.start)
-                                width = Dimension.fillToConstraints
-                            }
-                            .border(2.dp, Color.Red)
-                    )
-                    Box(
-                        modifier = Modifier
-                            .constrainAs(progressBar) {
-                                top.linkTo(title.bottom)
-                                start.linkTo(parent.start)
-                                end.linkTo(expander.start)
-                                width = Dimension.fillToConstraints
-                                height = Dimension.value(10.dp)
-                            }
-                            .background(Color.Yellow)
-                    )
-                    // expander image button
-                    Box(modifier = Modifier
-                        .constrainAs(expander) {
-                            top.linkTo(parent.top)
-                            start.linkTo(progressBar.end)
-                            end.linkTo(parent.end)
-                            width = Dimension.value(28.dp)
-                            height = Dimension.value(28.dp)
-                        }
-                        .background(Color.Cyan)
-                    )
-                }
-            }
-        }
-    }
-}
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 3a83ec4..21f35fb2 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 androidXMultiplatform {
diff --git a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt b/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt
deleted file mode 100644
index 1aec7be..0000000
--- a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/ButtonPaparazziScreenshotTest.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material
-
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ButtonPaparazziScreenshotTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun default_button() {
-        paparazzi.snapshot {
-            MaterialTheme {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 293af59..da3e1db 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -28,7 +28,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
-    id("AndroidXPaparazziPlugin")
 }
 
 androidXMultiplatform {
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt
deleted file mode 100644
index e386d4e..0000000
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/ButtonPaparazziTest.kt
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 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 androidx.compose.material3
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
-import androidx.testutils.paparazzi.androidxPaparazzi
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ButtonPaparazziTest {
-    @get:Rule
-    val paparazzi = androidxPaparazzi()
-
-    @Test
-    fun default_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun default_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(onClick = { }) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(onClick = { }, enabled = false) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(onClick = { }, enabled = false) {
-                        Text("Button")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun elevated_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun elevated_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_elevated_button_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}, enabled = false) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_elevated_button_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Box(
-                        Modifier.requiredSize(
-                            200.dp,
-                            100.dp
-                        ).wrapContentSize().testTag("elevated button")
-                    ) {
-                        ElevatedButton(onClick = {}, enabled = false) { Text("Elevated Button") }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun button_with_icon_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun button_with_icon_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_with_icon_light_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(lightColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
-                        enabled = false,
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun disabled_button_with_icon_dark_theme() {
-        paparazzi.snapshot {
-            MaterialTheme(darkColorScheme()) {
-                Surface {
-                    Button(
-                        onClick = { /* Do something! */ },
-                        contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
-                        enabled = false,
-                    ) {
-                        Icon(
-                            Icons.Filled.Favorite,
-                            contentDescription = "Localized description",
-                            modifier = Modifier.size(ButtonDefaults.IconSize)
-                        )
-                        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
-                        Text("Like")
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/external/paparazzi/paparazzi-agent/build.gradle b/external/paparazzi/paparazzi-agent/build.gradle
deleted file mode 100644
index 802d417..0000000
--- a/external/paparazzi/paparazzi-agent/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * This file was created using the `create_project.py` script located in the
- * `<AndroidX root>/development/project-creator` directory.
- *
- * Please use that script when creating a new project, rather than copying an existing project and
- * modifying its settings.
- */
-import androidx.build.LibraryType
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-}
-
-dependencies {
-    api(libs.kotlinStdlib)
-    api(libs.junit)
-    implementation(libs.byteBuddy)
-    implementation(libs.byteBuddyAgent)
-    testImplementation(libs.assertj)
-}
-
-androidx {
-    name = "Paparazzi Agent - AndroidX Fork"
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
diff --git a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt b/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt
deleted file mode 100644
index b79b5d5..0000000
--- a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/AgentTestRule.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.agent.ByteBuddyAgent
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-class AgentTestRule : TestRule {
-  override fun apply(
-    base: Statement,
-    description: Description
-  ) = object : Statement() {
-    override fun evaluate() {
-      ByteBuddyAgent.install()
-      InterceptorRegistrar.registerMethodInterceptors()
-      // interceptors are statically retained until test process finishes, so no need to cleanup
-      base.evaluate()
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt b/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt
deleted file mode 100644
index b436835..0000000
--- a/external/paparazzi/paparazzi-agent/src/main/java/app/cash/paparazzi/agent/InterceptorRegistrar.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.ByteBuddy
-import net.bytebuddy.dynamic.loading.ClassReloadingStrategy
-import net.bytebuddy.implementation.MethodDelegation
-import net.bytebuddy.matcher.ElementMatchers
-
-object InterceptorRegistrar {
-  private val byteBuddy = ByteBuddy()
-  private val methodInterceptors = mutableListOf<() -> Unit>()
-
-  fun addMethodInterceptor(
-    receiver: Class<*>,
-    methodName: String,
-    interceptor: Class<*>
-  ) = addMethodInterceptors(receiver, setOf(methodName to interceptor))
-
-  fun addMethodInterceptors(
-    receiver: Class<*>,
-    methodNamesToInterceptors: Set<Pair<String, Class<*>>>
-  ) {
-    methodInterceptors += {
-      var builder = byteBuddy
-        .redefine(receiver)
-
-      methodNamesToInterceptors.forEach {
-        builder = builder
-          .method(ElementMatchers.named(it.first))
-          .intercept(MethodDelegation.to(it.second))
-      }
-
-      builder
-        .make()
-        .load(receiver.classLoader, ClassReloadingStrategy.fromInstalledAgent())
-    }
-  }
-
-  fun registerMethodInterceptors() {
-    methodInterceptors.forEach { it.invoke() }
-  }
-
-  fun clearMethodInterceptors() {
-    methodInterceptors.clear()
-  }
-}
diff --git a/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt b/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt
deleted file mode 100644
index 1113073..0000000
--- a/external/paparazzi/paparazzi-agent/src/test/java/app/cash/paparazzi/agent/InterceptorRegistrarTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package app.cash.paparazzi.agent
-
-import net.bytebuddy.agent.ByteBuddyAgent
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-
-class InterceptorRegistrarTest {
-  @Before
-  fun setup() {
-    InterceptorRegistrar.addMethodInterceptors(
-      Utils::class.java,
-      setOf(
-        "log1" to Interceptor1::class.java,
-        "log2" to Interceptor2::class.java
-      )
-    )
-
-    ByteBuddyAgent.install()
-    InterceptorRegistrar.registerMethodInterceptors()
-  }
-
-  @Test
-  fun test() {
-    Utils.log1()
-    Utils.log2()
-
-    assertThat(logs).containsExactly("intercept1", "intercept2")
-  }
-
-  @After
-  fun teardown() {
-    InterceptorRegistrar.clearMethodInterceptors()
-  }
-
-  object Utils {
-    fun log1() {
-      logs += "original1"
-    }
-
-    fun log2() {
-      logs += "original2"
-    }
-  }
-
-  object Interceptor1 {
-    @Suppress("unused")
-    @JvmStatic
-    fun intercept() {
-      logs += "intercept1"
-    }
-  }
-
-  object Interceptor2 {
-    @Suppress("unused")
-    @JvmStatic
-    fun intercept() {
-      logs += "intercept2"
-    }
-  }
-
-  companion object {
-    private val logs = mutableListOf<String>()
-  }
-}
diff --git a/external/paparazzi/paparazzi/build.gradle b/external/paparazzi/paparazzi/build.gradle
deleted file mode 100644
index bc018ac..0000000
--- a/external/paparazzi/paparazzi/build.gradle
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * This file was created using the `create_project.py` script located in the
- * `<AndroidX root>/development/project-creator` directory.
- *
- * Please use that script when creating a new project, rather than copying an existing project and
- * modifying its settings.
- */
-import androidx.build.LibraryType
-import org.gradle.api.artifacts.transform.TransformParameters.None
-import java.util.zip.ZipInputStream
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-    id("com.google.devtools.ksp")
-    id("AndroidXComposePlugin")
-}
-
-androidx.configureAarAsJarForConfiguration("compileOnly")
-androidx.configureAarAsJarForConfiguration("testImplementation")
-
-dependencies {
-    api("androidx.annotation:annotation:1.3.0")
-    api("com.android.tools.layoutlib:layoutlib-api:27.2.2")
-    api("com.android.tools:common:27.1.2")
-    api(libs.androidToolsNinepatch)
-    api("com.android.tools:sdk-common:26.6.4")
-    api(libs.guava)
-    api(libs.junit)
-    api(libs.kotlinStdlib)
-    api(libs.kotlinCoroutinesCore)
-    api(libs.kxml2)
-    api(libs.okio)
-    api(libs.paparazziNativeJvm)
-    constraints {
-        implementation(libs.kotlinReflect) {
-            because("sdk-common depends on an old kotlin-reflect")
-        }
-    }
-
-    implementation(project(":external:paparazzi:paparazzi-agent"))
-    implementation(libs.jcodec)
-    implementation(libs.jcodecJavaSe)
-    implementation(libs.moshi)
-    implementation(libs.moshiAdapters)
-
-    compileOnlyAarAsJar("androidx.compose.runtime:runtime:1.2.1")
-    compileOnlyAarAsJar("androidx.compose.ui:ui:1.2.1")
-    compileOnly(project(":lifecycle:lifecycle-common"))
-    compileOnlyAarAsJar(project(":lifecycle:lifecycle-runtime"))
-    compileOnlyAarAsJar("androidx.savedstate:savedstate:1.2.0")
-
-    ksp(libs.moshiCodeGen)
-
-    testImplementation(libs.assertj)
-    testImplementationAarAsJar("androidx.compose.runtime:runtime:1.2.1")
-}
-
-androidx {
-    name = "Paparazzi - AndroidX Fork"
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
\ No newline at end of file
diff --git a/external/paparazzi/paparazzi/lint-baseline.xml b/external/paparazzi/paparazzi/lint-baseline.xml
deleted file mode 100644
index a53cf10..0000000
--- a/external/paparazzi/paparazzi/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="      Thread.sleep(100)"
-        errorLine2="             ~~~~~">
-        <location
-            file="src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt"/>
-    </issue>
-
-</issues>
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt
deleted file mode 100644
index 7345548..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/DeviceConfig.kt
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi
-
-import com.android.ide.common.rendering.api.HardwareConfig
-import com.android.ide.common.resources.configuration.CountryCodeQualifier
-import com.android.ide.common.resources.configuration.DensityQualifier
-import com.android.ide.common.resources.configuration.FolderConfiguration
-import com.android.ide.common.resources.configuration.KeyboardStateQualifier
-import com.android.ide.common.resources.configuration.LayoutDirectionQualifier
-import com.android.ide.common.resources.configuration.LocaleQualifier
-import com.android.ide.common.resources.configuration.NavigationMethodQualifier
-import com.android.ide.common.resources.configuration.NetworkCodeQualifier
-import com.android.ide.common.resources.configuration.NightModeQualifier
-import com.android.ide.common.resources.configuration.ScreenDimensionQualifier
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier
-import com.android.ide.common.resources.configuration.ScreenRatioQualifier
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier
-import com.android.ide.common.resources.configuration.TextInputMethodQualifier
-import com.android.ide.common.resources.configuration.TouchScreenQualifier
-import com.android.ide.common.resources.configuration.UiModeQualifier
-import com.android.ide.common.resources.configuration.VersionQualifier
-import com.android.resources.Density
-import com.android.resources.Keyboard
-import com.android.resources.KeyboardState
-import com.android.resources.LayoutDirection
-import com.android.resources.Navigation
-import com.android.resources.NightMode
-import com.android.resources.NightMode.NOTNIGHT
-import com.android.resources.ScreenOrientation
-import com.android.resources.ScreenRatio
-import com.android.resources.ScreenSize
-import com.android.resources.TouchScreen
-import com.android.resources.UiMode
-import com.google.android.collect.Maps
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
-import java.util.Properties
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-import org.xmlpull.v1.XmlPullParserFactory
-
-/**
- * Provides [FolderConfiguration] and [HardwareConfig] for various devices. Also provides utility
- * methods to parse `build.prop` and `attrs.xml` to generate the appropriate maps.
- *
- * Defaults are for a Nexus 4 device.
- */
-data class DeviceConfig(
-  val screenHeight: Int = 1280,
-  val screenWidth: Int = 768,
-  val xdpi: Int = 320,
-  val ydpi: Int = 320,
-  val orientation: ScreenOrientation = ScreenOrientation.PORTRAIT,
-  val nightMode: NightMode = NOTNIGHT,
-  val density: Density = Density.XHIGH,
-  val fontScale: Float = 1f,
-  val layoutDirection: LayoutDirection = LayoutDirection.LTR,
-  val locale: String = "en",
-  val ratio: ScreenRatio = ScreenRatio.NOTLONG,
-  val size: ScreenSize = ScreenSize.NORMAL,
-  val keyboard: Keyboard = Keyboard.NOKEY,
-  val touchScreen: TouchScreen = TouchScreen.FINGER,
-  val keyboardState: KeyboardState = KeyboardState.SOFT,
-  val softButtons: Boolean = true,
-  val navigation: Navigation = Navigation.NONAV,
-  val released: String = "November 13, 2012"
-) {
-  val folderConfiguration: FolderConfiguration
-    get() = FolderConfiguration.createDefault()
-      .apply {
-        densityQualifier = DensityQualifier(density)
-        navigationMethodQualifier = NavigationMethodQualifier(navigation)
-        screenDimensionQualifier = when {
-          screenWidth > screenHeight -> ScreenDimensionQualifier(screenWidth, screenHeight)
-          else -> ScreenDimensionQualifier(screenHeight, screenWidth)
-        }
-        screenRatioQualifier = ScreenRatioQualifier(ratio)
-        screenSizeQualifier = ScreenSizeQualifier(size)
-        textInputMethodQualifier = TextInputMethodQualifier(keyboard)
-        touchTypeQualifier = TouchScreenQualifier(touchScreen)
-        keyboardStateQualifier = KeyboardStateQualifier(keyboardState)
-        screenOrientationQualifier = ScreenOrientationQualifier(orientation)
-
-        updateScreenWidthAndHeight()
-        uiModeQualifier = UiModeQualifier(UiMode.NORMAL)
-        nightModeQualifier = NightModeQualifier(nightMode)
-        countryCodeQualifier = CountryCodeQualifier()
-        layoutDirectionQualifier = LayoutDirectionQualifier(layoutDirection)
-        networkCodeQualifier = NetworkCodeQualifier()
-        localeQualifier = LocaleQualifier.getQualifier(locale)
-        versionQualifier = VersionQualifier()
-      }
-
-  val hardwareConfig: HardwareConfig
-    get() = HardwareConfig(
-      screenWidth, screenHeight, density, xdpi.toFloat(), ydpi.toFloat(), size,
-      orientation, null, softButtons
-    )
-
-  /**
-   * Device specs per:
-   * https://android.googlesource.com/platform/tools/base/+/mirror-goog-studio-master-dev/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
-   *
-   * Release dates obtained from Wikipedia.
-   */
-
-  companion object {
-    @JvmField
-    val NEXUS_4 = DeviceConfig()
-
-    @JvmField
-    val NEXUS_5 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 445,
-      ydpi = 445,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.XXHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 31, 2013"
-    )
-
-    @JvmField
-    val NEXUS_7 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1200,
-      xdpi = 323,
-      ydpi = 323,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.LARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "July 26, 2013"
-    )
-
-    @JvmField
-    val NEXUS_10 = DeviceConfig(
-      screenHeight = 1600,
-      screenWidth = 2560,
-      xdpi = 300,
-      ydpi = 300,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.XLARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "November 13, 2012"
-    )
-
-    @JvmField
-    val NEXUS_5_LAND = DeviceConfig(
-      screenHeight = 1080,
-      screenWidth = 1920,
-      xdpi = 445,
-      ydpi = 445,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XXHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 31, 2013"
-    )
-
-    @JvmField
-    val NEXUS_7_2012 = DeviceConfig(
-      screenHeight = 1280,
-      screenWidth = 800,
-      xdpi = 195,
-      ydpi = 200,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.TV,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.LARGE,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "July 13, 2012"
-    )
-
-    @JvmField
-    val PIXEL_C = DeviceConfig(
-      screenHeight = 1800,
-      screenWidth = 2560,
-      xdpi = 308,
-      ydpi = 308,
-      orientation = ScreenOrientation.LANDSCAPE,
-      density = Density.XHIGH,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.XLARGE,
-      keyboard = Keyboard.QWERTY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "December 8, 2015"
-    )
-
-    @JvmField
-    val PIXEL = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 440,
-      ydpi = 440,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 20, 2016"
-    )
-
-    @JvmField
-    val PIXEL_XL = DeviceConfig(
-      screenHeight = 2560,
-      screenWidth = 1440,
-      xdpi = 534,
-      ydpi = 534,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 20, 2016"
-    )
-
-    @JvmField
-    val PIXEL_2 = DeviceConfig(
-      screenHeight = 1920,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 443,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.NOTLONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 19, 2017"
-    )
-
-    @JvmField
-    val PIXEL_2_XL = DeviceConfig(
-      screenHeight = 2880,
-      screenWidth = 1440,
-      xdpi = 537,
-      ydpi = 537,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 19, 2017"
-    )
-
-    @JvmField
-    val PIXEL_3 = DeviceConfig(
-      screenHeight = 2160,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 442,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 18, 2018"
-    )
-
-    @JvmField
-    val PIXEL_3_XL = DeviceConfig(
-      screenHeight = 2960,
-      screenWidth = 1440,
-      xdpi = 522,
-      ydpi = 522,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 18, 2018"
-    )
-
-    @JvmField
-    val PIXEL_3A = DeviceConfig(
-      screenHeight = 2220,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "May 7, 2019"
-    )
-
-    @JvmField
-    val PIXEL_3A_XL = DeviceConfig(
-      screenHeight = 2160,
-      screenWidth = 1080,
-      xdpi = 397,
-      ydpi = 400,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_400,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "May 7, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4 = DeviceConfig(
-      screenHeight = 2280,
-      screenWidth = 1080,
-      xdpi = 444,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 24, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4_XL = DeviceConfig(
-      screenHeight = 3040,
-      screenWidth = 1440,
-      xdpi = 537,
-      ydpi = 537,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 24, 2019"
-    )
-
-    @JvmField
-    val PIXEL_4A = DeviceConfig(
-      screenHeight = 2340,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "August 20, 2020"
-    )
-
-    @JvmField
-    val PIXEL_5 = DeviceConfig(
-      screenHeight = 2340,
-      screenWidth = 1080,
-      xdpi = 442,
-      ydpi = 444,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_440,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 15, 2020"
-    )
-
-    @JvmField
-    val PIXEL_6 = DeviceConfig(
-      screenHeight = 2400,
-      screenWidth = 1080,
-      xdpi = 406,
-      ydpi = 411,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_420,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 28, 2021"
-    )
-
-    @JvmField
-    val PIXEL_6_PRO = DeviceConfig(
-      screenHeight = 3120,
-      screenWidth = 1440,
-      xdpi = 512,
-      ydpi = 512,
-      orientation = ScreenOrientation.PORTRAIT,
-      density = Density.DPI_560,
-      ratio = ScreenRatio.LONG,
-      size = ScreenSize.NORMAL,
-      keyboard = Keyboard.NOKEY,
-      touchScreen = TouchScreen.FINGER,
-      keyboardState = KeyboardState.SOFT,
-      softButtons = true,
-      navigation = Navigation.NONAV,
-      released = "October 28, 2021"
-    )
-
-    private const val TAG_ATTR = "attr"
-    private const val TAG_ENUM = "enum"
-    private const val TAG_FLAG = "flag"
-    private const val ATTR_NAME = "name"
-    private const val ATTR_VALUE = "value"
-
-    @Throws(IOException::class)
-    fun loadProperties(path: File): Map<String, String> {
-      val p = Properties()
-      val map = Maps.newHashMap<String, String>()
-      p.load(FileInputStream(path))
-      for (key in p.stringPropertyNames()) {
-        map[key] = p.getProperty(key)
-      }
-      return map
-    }
-
-    @Throws(IOException::class, XmlPullParserException::class)
-    fun getEnumMap(path: File): Map<String, Map<String, Int>> {
-      val map = mutableMapOf<String, MutableMap<String, Int>>()
-
-      val xmlPullParser = XmlPullParserFactory.newInstance()
-        .newPullParser()
-      xmlPullParser.setInput(FileInputStream(path), null)
-      var eventType = xmlPullParser.eventType
-      var attr: String? = null
-      while (eventType != XmlPullParser.END_DOCUMENT) {
-        if (eventType == XmlPullParser.START_TAG) {
-          if (TAG_ATTR == xmlPullParser.name) {
-            attr = xmlPullParser.getAttributeValue(null, ATTR_NAME)
-          } else if (TAG_ENUM == xmlPullParser.name || TAG_FLAG == xmlPullParser.name) {
-            val name = xmlPullParser.getAttributeValue(null, ATTR_NAME)
-            val value = xmlPullParser.getAttributeValue(null, ATTR_VALUE)
-            // Integer.decode cannot handle "ffffffff", see JDK issue 6624867
-            val i = (java.lang.Long.decode(value) as Long).toInt()
-            require(attr != null)
-            var attributeMap: MutableMap<String, Int>? = map[attr]
-            if (attributeMap == null) {
-              attributeMap = Maps.newHashMap()
-              map[attr] = attributeMap
-            }
-            attributeMap!![name] = i
-          }
-        } else if (eventType == XmlPullParser.END_TAG) {
-          if (TAG_ATTR == xmlPullParser.name) {
-            attr = null
-          }
-        }
-        eventType = xmlPullParser.next()
-      }
-
-      return map
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt
deleted file mode 100644
index 8cfae19..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Environment.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import java.io.File
-import java.io.FileNotFoundException
-import java.nio.file.Path
-import java.nio.file.Paths
-import java.util.Locale
-import kotlin.io.path.exists
-
-data class Environment(
-  val platformDir: String,
-  val appTestDir: String,
-  val resDir: String,
-  val assetsDir: String,
-  val compileSdkVersion: Int,
-  val resourcePackageNames: List<String>
-) {
-  init {
-    val platformDirPath = Path.of(platformDir)
-    if (!platformDirPath.exists()) {
-      val elements = platformDirPath.nameCount
-      val platform = platformDirPath.subpath(elements - 1, elements)
-      val platformVersion = platform.toString().split("-").last()
-      throw FileNotFoundException(
-        "Missing platform version $platformVersion. " +
-            "Install with sdkmanager --install \"platforms;$platform\""
-      )
-    }
-  }
-}
-
-@Suppress("unused")
-fun androidHome() = System.getenv("ANDROID_SDK_ROOT")
-  ?: System.getenv("ANDROID_HOME")
-  ?: androidSdkPath()
-
-fun detectEnvironment(): Environment {
-  checkInstalledJvm()
-
-  val resourcesFile = File(System.getProperty("paparazzi.test.resources"))
-  val configLines = resourcesFile.readLines()
-
-  val appTestDir = Paths.get(System.getProperty("user.dir"))
-  val androidHome = Paths.get(androidHome())
-  return Environment(
-    platformDir = androidHome.resolve(configLines[3]).toString(),
-    appTestDir = appTestDir.toString(),
-    resDir = appTestDir.resolve(configLines[1]).toString(),
-    assetsDir = appTestDir.resolve(configLines[4]).toString(),
-    compileSdkVersion = configLines[2].toInt(),
-    resourcePackageNames = configLines[5].split(",")
-  )
-}
-
-private fun androidSdkPath(): String {
-  val osName = System.getProperty("os.name").lowercase(Locale.US)
-  val sdkPathDir = if (osName.startsWith("windows")) {
-    "\\AppData\\Local\\Android\\Sdk"
-  } else if (osName.startsWith("mac")) {
-    "/Library/Android/sdk"
-  } else {
-    "/Android/Sdk"
-  }
-  val homeDir = System.getProperty("user.home")
-  return homeDir + sdkPathDir
-}
-
-private fun checkInstalledJvm() {
-  val feature = try {
-    // Runtime#version() only available as of Java 9.
-    val version = Runtime::class.java.getMethod("version").invoke(null)
-    // Runtime.Version#feature() only available as of Java 10.
-    version.javaClass.getMethod("feature").invoke(version) as Int
-  } catch (e: NoSuchMethodException) {
-    -1
-  }
-
-  if (feature < 11) {
-    throw IllegalStateException(
-      "Unsupported JRE detected! Please install and run Paparazzi test suites on JDK 11+."
-    )
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt
deleted file mode 100644
index 62c0b62..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Flags.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package app.cash.paparazzi
-
-object Flags {
-  const val DEBUG_LINKED_OBJECTS = "app.cash.paparazzi.debug.linked.objects"
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt
deleted file mode 100644
index c43f4fb..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import app.cash.paparazzi.SnapshotHandler.FrameHandler
-import app.cash.paparazzi.internal.PaparazziJson
-import com.google.common.base.CharMatcher
-import java.awt.image.BufferedImage
-import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.UUID
-import javax.imageio.ImageIO
-import okio.BufferedSink
-import okio.HashingSink
-import okio.blackholeSink
-import okio.buffer
-import okio.sink
-import okio.source
-import org.jcodec.api.awt.AWTSequenceEncoder
-
-/**
- * Creates an HTML report that avoids writing files that have already been written.
- *
- * Images and videos are named by hashes of their contents. Paparazzi won't write two images or videos with the same
- * contents. Note that the images/ directory includes the individual frames of each video.
- *
- * Runs are named by their date.
- *
- * ```
- * images
- *   088c60580f06efa95c37fd8e754074729ee74a06.png
- *   93f9a81cb594280f4b3898d90dfad8c8ea969b01.png
- *   22d37abd0841ba2a8d0bd635954baf7cbfaa269b.png
- *   a4769e43cc5901ef28c0d46c46a44ea6429cbccc.png
- * videos
- *   d1cddc5da2224053f2af51f4e69a76de4e61fc41.mov
- * runs
- *   20190626002322_b9854e.js
- *   20190626002345_b1e882.js
- * index.html
- * index.js
- * paparazzi.js
- * ```
- */
-class HtmlReportWriter @JvmOverloads constructor(
-  private val runName: String = defaultRunName(),
-  private val rootDirectory: File = File("build/reports/paparazzi"),
-  snapshotRootDirectory: File = File("src/test/snapshots")
-) : SnapshotHandler {
-  private val runsDirectory: File = File(rootDirectory, "runs")
-  private val imagesDirectory: File = File(rootDirectory, "images")
-  private val videosDirectory: File = File(rootDirectory, "videos")
-
-  private val goldenImagesDirectory = File(snapshotRootDirectory, "images")
-  private val goldenVideosDirectory = File(snapshotRootDirectory, "videos")
-
-  private val shots = mutableListOf<Snapshot>()
-
-  private val isRecording: Boolean =
-    System.getProperty("paparazzi.test.record")?.toBoolean() == true
-
-  init {
-    runsDirectory.mkdirs()
-    imagesDirectory.mkdirs()
-    videosDirectory.mkdirs()
-    writeStaticFiles()
-    writeRunJs()
-    writeIndexJs()
-  }
-
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler {
-    return object : FrameHandler {
-      val hashes = mutableListOf<String>()
-
-      override fun handle(image: BufferedImage) {
-        hashes += writeImage(image)
-      }
-
-      override fun close() {
-        if (hashes.isEmpty()) return
-
-        val shot = if (hashes.size == 1) {
-          val original = File(imagesDirectory, "${hashes[0]}.png")
-          if (isRecording) {
-            val goldenFile = File(goldenImagesDirectory, snapshot.toFileName("_", "png"))
-            original.copyTo(goldenFile, overwrite = true)
-          }
-          snapshot.copy(file = original.toJsonPath())
-        } else {
-          val hash = writeVideo(hashes, fps)
-
-          if (isRecording) {
-            for ((index, frameHash) in hashes.withIndex()) {
-              val originalFrame = File(imagesDirectory, "$frameHash.png")
-              val frameSnapshot = snapshot.copy(name = "${snapshot.name} $index")
-              val goldenFile = File(goldenImagesDirectory, frameSnapshot.toFileName("_", "png"))
-              if (!goldenFile.exists()) {
-                originalFrame.copyTo(goldenFile)
-              }
-            }
-          }
-          val original = File(videosDirectory, "$hash.mov")
-          if (isRecording) {
-            val goldenFile = File(goldenVideosDirectory, snapshot.toFileName("_", "mov"))
-            if (!goldenFile.exists()) {
-              original.copyTo(goldenFile)
-            }
-          }
-          snapshot.copy(file = original.toJsonPath())
-        }
-
-        shots += shot
-      }
-    }
-  }
-
-  /** Returns the hash of the image. */
-  private fun writeImage(image: BufferedImage): String {
-    val hash = hash(image)
-    val file = File(imagesDirectory, "$hash.png")
-    if (!file.exists()) {
-      file.writeAtomically(image)
-    }
-    return hash
-  }
-
-  /** Returns a SHA-1 hash of the pixels of [image]. */
-  private fun hash(image: BufferedImage): String {
-    val hashingSink = HashingSink.sha1(blackholeSink())
-    hashingSink.buffer().use { sink ->
-      for (y in 0 until image.height) {
-        for (x in 0 until image.width) {
-          sink.writeInt(image.getRGB(x, y))
-        }
-      }
-    }
-    return hashingSink.hash.hex()
-  }
-
-  private fun writeVideo(
-    frameHashes: List<String>,
-    fps: Int
-  ): String {
-    val hash = hash(frameHashes)
-    val file = File(videosDirectory, "$hash.mov")
-    if (!file.exists()) {
-      val tmpFile = File(videosDirectory, "$hash.mov.tmp")
-      val encoder = AWTSequenceEncoder.createSequenceEncoder(tmpFile, fps)
-      for (frameHash in frameHashes) {
-        val frame = ImageIO.read(File(imagesDirectory, "$frameHash.png"))
-        encoder.encodeImage(frame)
-      }
-      encoder.finish()
-      tmpFile.renameTo(file)
-    }
-    return hash
-  }
-
-  /** Returns a SHA-1 hash of [lines]. */
-  private fun hash(lines: List<String>): String {
-    val hashingSink = HashingSink.sha1(blackholeSink())
-    hashingSink.buffer().use { sink ->
-      for (hash in lines) {
-        sink.writeUtf8(hash)
-        sink.writeUtf8("\n")
-      }
-    }
-    return hashingSink.hash.hex()
-  }
-
-  /** Release all resources and block until everything has been written to the file system. */
-  override fun close() {
-    writeRunJs()
-  }
-
-  /**
-   * Emits the all runs index, which reads like JSON with an executable header.
-   *
-   * ```
-   * window.all_runs = [
-   *   "20190319153912aaab",
-   *   "20190319153917bcfe"
-   * ];
-   * ```
-   */
-  private fun writeIndexJs() {
-    val runNames = mutableListOf<String>()
-    val runs = runsDirectory.list().sorted()
-    for (run in runs) {
-      if (run.endsWith(".js")) {
-        runNames += run.substring(0, run.length - 3)
-      }
-    }
-
-    File(rootDirectory, "index.js").writeAtomically {
-      writeUtf8("window.all_runs = ")
-      PaparazziJson.listOfStringsAdapter.toJson(this, runNames)
-      writeUtf8(";")
-    }
-  }
-
-  /**
-   * Emits a run index, which reads like JSON with an executable header.
-   *
-   * ```
-   * window.runs["20190319153912aaab"] = [
-   *   {
-   *     "name": "loading",
-   *     "testName": "app.cash.CelebrityTest#testSettings",
-   *     "timestamp": "2019-03-20T10:27:43Z",
-   *     "tags": ["redesign"],
-   *     "file": "loading.png"
-   *   },
-   *   {
-   *     "name": "error",
-   *     "testName": "app.cash.CelebrityTest#testSettings",
-   *     "timestamp": "2019-03-20T10:27:43Z",
-   *     "tags": ["redesign"],
-   *     "file": "error.png"
-   *   }
-   * ];
-   * ```
-   */
-  private fun writeRunJs() {
-    val runJs = File(runsDirectory, "${runName.sanitizeForFilename()}.js")
-    runJs.writeAtomically {
-      writeUtf8("window.runs[\"$runName\"] = ")
-      PaparazziJson.listOfShotsAdapter.toJson(this, shots)
-      writeUtf8(";")
-    }
-  }
-
-  private fun writeStaticFiles() {
-    for (staticFile in listOf("index.html", "paparazzi.js")) {
-      File(rootDirectory, staticFile).writeAtomically {
-        writeAll(HtmlReportWriter::class.java.classLoader.getResourceAsStream(staticFile).source())
-      }
-    }
-  }
-
-  private fun File.writeAtomically(bufferedImage: BufferedImage) {
-    val tmpFile = File(parentFile, "$name.tmp")
-    ImageIO.write(bufferedImage, "PNG", tmpFile)
-    delete()
-    tmpFile.renameTo(this)
-  }
-
-  private fun File.writeAtomically(writerAction: BufferedSink.() -> Unit) {
-    val tmpFile = File(parentFile, "$name.tmp")
-    tmpFile.sink()
-      .buffer()
-      .use { sink ->
-        sink.writerAction()
-      }
-    delete()
-    tmpFile.renameTo(this)
-  }
-
-  private fun File.toJsonPath(): String = relativeTo(rootDirectory).invariantSeparatorsPath
-}
-
-internal fun defaultRunName(): String {
-  val now = Date()
-  val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(now)
-  val token = UUID.randomUUID().toString().substring(0, 6)
-  return "${timestamp}_$token"
-}
-
-internal val filenameSafeChars = CharMatcher.inRange('a', 'z')
-  .or(CharMatcher.inRange('0', '9'))
-  .or(CharMatcher.anyOf("_-.~@^()[]{}:;,"))
-
-internal fun String.sanitizeForFilename(): String? {
-  return filenameSafeChars.negate().replaceFrom(lowercase(Locale.US), '_')
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
deleted file mode 100644
index 2c9dec41..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import android.animation.AnimationHandler
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Bitmap
-import android.os.Handler_Delegate
-import android.os.SystemClock_Delegate
-import android.util.AttributeSet
-import android.util.DisplayMetrics
-import android.view.BridgeInflater
-import android.view.Choreographer
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.widget.FrameLayout
-import androidx.annotation.LayoutRes
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ComposeView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.lifecycle.setViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
-import androidx.savedstate.SavedStateRegistryController
-import androidx.savedstate.SavedStateRegistryOwner
-import androidx.savedstate.setViewTreeSavedStateRegistryOwner
-import app.cash.paparazzi.agent.AgentTestRule
-import app.cash.paparazzi.agent.InterceptorRegistrar
-import app.cash.paparazzi.internal.ChoreographerDelegateInterceptor
-import app.cash.paparazzi.internal.EditModeInterceptor
-import app.cash.paparazzi.internal.IInputMethodManagerInterceptor
-import app.cash.paparazzi.internal.ImageUtils
-import app.cash.paparazzi.internal.MatrixMatrixMultiplicationInterceptor
-import app.cash.paparazzi.internal.MatrixVectorMultiplicationInterceptor
-import app.cash.paparazzi.internal.PaparazziCallback
-import app.cash.paparazzi.internal.PaparazziLogger
-import app.cash.paparazzi.internal.Renderer
-import app.cash.paparazzi.internal.ResourcesInterceptor
-import app.cash.paparazzi.internal.ServiceManagerInterceptor
-import app.cash.paparazzi.internal.SessionParamsBuilder
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.ide.common.rendering.api.RenderSession
-import com.android.ide.common.rendering.api.Result
-import com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import com.android.internal.lang.System_Delegate
-import com.android.layoutlib.bridge.Bridge
-import com.android.layoutlib.bridge.Bridge.cleanupThread
-import com.android.layoutlib.bridge.Bridge.prepareThread
-import com.android.layoutlib.bridge.BridgeRenderSession
-import com.android.layoutlib.bridge.impl.RenderAction
-import com.android.layoutlib.bridge.impl.RenderSessionImpl
-import java.awt.image.BufferedImage
-import java.util.Date
-import java.util.concurrent.TimeUnit
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-class Paparazzi @JvmOverloads constructor(
-  private val environment: Environment = detectEnvironment(),
-  private val deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5,
-  private val theme: String = "android:Theme.Material.NoActionBar.Fullscreen",
-  private val renderingMode: RenderingMode = RenderingMode.NORMAL,
-  private val appCompatEnabled: Boolean = true,
-  private val maxPercentDifference: Double = 0.1,
-  private val snapshotHandler: SnapshotHandler = determineHandler(maxPercentDifference),
-  private val renderExtensions: Set<RenderExtension> = setOf()
-) : TestRule {
-  private val logger = PaparazziLogger()
-  private lateinit var renderSession: RenderSessionImpl
-  private lateinit var bridgeRenderSession: RenderSession
-  private var testName: TestName? = null
-
-  val layoutInflater: LayoutInflater
-    get() = RenderAction.getCurrentContext().getSystemService("layout_inflater") as BridgeInflater
-
-  val resources: Resources
-    get() = RenderAction.getCurrentContext().resources
-
-  val context: Context
-    get() = RenderAction.getCurrentContext()
-
-  /**
-   * The root layout that test views will be placed into. The FrameLayout is dynamically set to
-   * `wrap_content` if the `renderMode` is `RenderingMode.SizeAction.SHRINK` in the appropriate
-   * direction, otherwise it is set to `match_parent`.
-   */
-  private val contentRoot = """
-        |<?xml version="1.0" encoding="utf-8"?>
-        |<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        |              android:layout_width="${renderingMode.horizAction.toAttrValue()}"
-        |              android:layout_height="${renderingMode.vertAction.toAttrValue()}"/>
-  """.trimMargin()
-
-  private fun RenderingMode.SizeAction.toAttrValue() =
-    if (this == RenderingMode.SizeAction.SHRINK) "wrap_content" else "match_parent"
-
-  override fun apply(
-    base: Statement,
-    description: Description
-  ): Statement {
-    val statement = object : Statement() {
-      override fun evaluate() {
-        prepare(description)
-        try {
-          base.evaluate()
-        } finally {
-          close()
-          logger.assertNoErrors()
-        }
-      }
-    }
-
-    return if (!isInitialized) {
-      registerFontLookupInterceptionIfResourceCompatDetected()
-      registerViewEditModeInterception()
-      registerMatrixMultiplyInterception()
-      registerChoreographerDelegateInterception()
-      registerServiceManagerInterception()
-      registerIInputMethodManagerInterception()
-
-      val outerRule = AgentTestRule()
-      outerRule.apply(statement, description)
-    } else {
-      statement
-    }
-  }
-
-  fun prepare(description: Description) {
-    forcePlatformSdkVersion(environment.compileSdkVersion)
-
-    val layoutlibCallback = PaparazziCallback(logger, environment.resourcePackageNames)
-    layoutlibCallback.initResources()
-
-    testName = description.toTestName()
-
-    if (!isInitialized) {
-      renderer = Renderer(environment, layoutlibCallback, logger, maxPercentDifference)
-      sessionParamsBuilder = renderer.prepare()
-    }
-
-    sessionParamsBuilder = sessionParamsBuilder
-      .copy(
-        layoutPullParser = LayoutPullParser.createFromString(contentRoot),
-        deviceConfig = deviceConfig,
-        renderingMode = renderingMode
-      )
-      .withTheme(theme)
-
-    val sessionParams = sessionParamsBuilder.build()
-    renderSession = createRenderSession(sessionParams)
-    prepareThread()
-    renderSession.init(sessionParams.timeout)
-    Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEVICE_STABLE)
-
-    // requires LayoutInflater to be created, which is a side-effect of RenderSessionImpl.init()
-    if (appCompatEnabled) {
-      initializeAppCompatIfPresent()
-    }
-
-    bridgeRenderSession = createBridgeSession(renderSession, renderSession.inflate())
-  }
-
-  fun close() {
-    testName = null
-    renderSession.release()
-    bridgeRenderSession.dispose()
-    cleanupThread()
-    snapshotHandler.close()
-
-    renderer.dumpDelegates()
-  }
-
-  @Suppress("UNCHECKED_CAST")
-  fun <V : View> inflate(@LayoutRes layoutId: Int): V =
-    layoutInflater.inflate(layoutId, null) as V
-
-  fun snapshot(name: String? = null, composable: @Composable () -> Unit) {
-    val hostView = ComposeView(context)
-    // During onAttachedToWindow, AbstractComposeView will attempt to resolve its parent's
-    // CompositionContext, which requires first finding the "content view", then using that to
-    // find a root view with a ViewTreeLifecycleOwner
-    val parent = FrameLayout(context).apply { id = android.R.id.content }
-    parent.addView(
-      hostView,
-      renderingMode.horizAction.toLayoutParams(),
-      renderingMode.vertAction.toLayoutParams()
-    )
-    PaparazziComposeOwner.register(parent)
-    hostView.setContent(composable)
-
-    try {
-      snapshot(parent, name)
-    } finally {
-      forceReleaseComposeReferenceLeaks()
-    }
-  }
-
-  private fun RenderingMode.SizeAction.toLayoutParams() =
-    if (this == RenderingMode.SizeAction.SHRINK) {
-      LayoutParams.WRAP_CONTENT
-    } else {
-      LayoutParams.MATCH_PARENT
-    }
-
-  @JvmOverloads
-  fun snapshot(view: View, name: String? = null) {
-    takeSnapshots(view, name, 0, -1, 1)
-  }
-
-  @JvmOverloads
-  fun gif(
-    view: View,
-    name: String? = null,
-    start: Long = 0L,
-    end: Long = 500L,
-    fps: Int = 30
-  ) {
-    // Add one to the frame count so we get the last frame. Otherwise a 1 second, 60 FPS animation
-    // our 60th frame will be at time 983 ms, and we want our last frame to be 1,000 ms. This gets
-    // us 61 frames for a 1 second animation, 121 frames for a 2 second animation, etc.
-    val durationMillis = (end - start).toInt()
-    val frameCount = (durationMillis * fps) / 1000 + 1
-    val startNanos = TimeUnit.MILLISECONDS.toNanos(start)
-    takeSnapshots(view, name, startNanos, fps, frameCount)
-  }
-
-  fun unsafeUpdateConfig(
-    deviceConfig: DeviceConfig? = null,
-    theme: String? = null,
-    renderingMode: RenderingMode? = null
-  ) {
-    require(deviceConfig != null || theme != null || renderingMode != null) {
-      "Calling unsafeUpdateConfig requires at least one non-null argument."
-    }
-
-    renderSession.release()
-    bridgeRenderSession.dispose()
-    cleanupThread()
-
-    sessionParamsBuilder = sessionParamsBuilder
-      .copy(
-        // Required to reset underlying parser stream
-        layoutPullParser = LayoutPullParser.createFromString(contentRoot)
-      )
-
-    if (deviceConfig != null) {
-      sessionParamsBuilder = sessionParamsBuilder.copy(deviceConfig = deviceConfig)
-    }
-
-    if (theme != null) {
-      sessionParamsBuilder = sessionParamsBuilder.withTheme(theme)
-    }
-
-    if (renderingMode != null) {
-      sessionParamsBuilder = sessionParamsBuilder.copy(renderingMode = renderingMode)
-    }
-
-    val sessionParams = sessionParamsBuilder.build()
-    renderSession = createRenderSession(sessionParams)
-    prepareThread()
-    renderSession.init(sessionParams.timeout)
-    Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEVICE_STABLE)
-    bridgeRenderSession = createBridgeSession(renderSession, renderSession.inflate())
-  }
-
-  private fun takeSnapshots(
-    view: View,
-    name: String?,
-    startNanos: Long,
-    fps: Int,
-    frameCount: Int
-  ) {
-    val snapshot = Snapshot(name, testName!!, Date())
-
-    val frameHandler = snapshotHandler.newFrameHandler(snapshot, frameCount, fps)
-    frameHandler.use {
-      val viewGroup = bridgeRenderSession.rootViews[0].viewObject as ViewGroup
-      val modifiedView = renderExtensions.fold(view) { view, renderExtension ->
-        renderExtension.renderView(view)
-      }
-
-      System_Delegate.setBootTimeNanos(0L)
-      try {
-        withTime(0L) {
-          // Initialize the choreographer at time=0.
-        }
-
-        viewGroup.addView(modifiedView)
-        for (frame in 0 until frameCount) {
-          val nowNanos = (startNanos + (frame * 1_000_000_000.0 / fps)).toLong()
-          withTime(nowNanos) {
-            val result = renderSession.render(true)
-            if (result.status == ERROR_UNKNOWN) {
-              throw result.exception
-            }
-
-            val image = bridgeRenderSession.image
-            frameHandler.handle(scaleImage(image))
-          }
-        }
-      } finally {
-        viewGroup.removeView(modifiedView)
-        AnimationHandler.sAnimatorHandler.set(null)
-      }
-    }
-  }
-
-  private fun withTime(
-    timeNanos: Long,
-    block: () -> Unit
-  ) {
-    val frameNanos = TIME_OFFSET_NANOS + timeNanos
-
-    // Execute the block at the requested time.
-    System_Delegate.setNanosTime(frameNanos)
-
-    val choreographer = Choreographer.getInstance()
-    val areCallbacksRunningField = choreographer::class.java.getDeclaredField("mCallbacksRunning")
-    areCallbacksRunningField.isAccessible = true
-
-    try {
-      areCallbacksRunningField.setBoolean(choreographer, true)
-
-      // https://android.googlesource.com/platform/frameworks/layoutlib/+/d58aa4703369e109b24419548f38b422d5a44738/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java#171
-      // BridgeRenderSession.executeCallbacks aggressively tears down the main Looper and BridgeContext, so we call the static delegates ourselves.
-      Handler_Delegate.executeCallbacks()
-      val currentTimeMs = SystemClock_Delegate.uptimeMillis()
-      val choreographerCallbacks =
-        RenderAction.getCurrentContext().sessionInteractiveData.choreographerCallbacks
-      choreographerCallbacks.execute(currentTimeMs, Bridge.getLog())
-
-      block()
-    } catch (e: Throwable) {
-      Bridge.getLog().error("broken", "Failed executing Choreographer#doFrame", e, null, null)
-      throw e
-    } finally {
-      areCallbacksRunningField.setBoolean(choreographer, false)
-    }
-  }
-
-  private fun createRenderSession(sessionParams: SessionParams): RenderSessionImpl {
-    val renderSession = RenderSessionImpl(sessionParams)
-    renderSession.setElapsedFrameTimeNanos(0L)
-    RenderSessionImpl::class.java
-      .getDeclaredField("mFirstFrameExecuted")
-      .apply {
-        isAccessible = true
-        set(renderSession, true)
-      }
-    return renderSession
-  }
-
-  private fun createBridgeSession(
-    renderSession: RenderSessionImpl,
-    result: Result
-  ): BridgeRenderSession {
-    try {
-      val bridgeSessionClass = Class.forName("com.android.layoutlib.bridge.BridgeRenderSession")
-      val constructor =
-        bridgeSessionClass.getDeclaredConstructor(RenderSessionImpl::class.java, Result::class.java)
-      constructor.isAccessible = true
-      return constructor.newInstance(renderSession, result) as BridgeRenderSession
-    } catch (e: Exception) {
-      throw RuntimeException(e)
-    }
-  }
-
-  private fun scaleImage(image: BufferedImage): BufferedImage {
-    val scale = ImageUtils.getThumbnailScale(image)
-    // Only scale images down so we don't waste storage space enlarging smaller layouts.
-    return if (scale < 1f) ImageUtils.scale(image, scale, scale) else image
-  }
-
-  private fun Description.toTestName(): TestName {
-    val fullQualifiedName = className
-    val packageName = fullQualifiedName.substringBeforeLast('.', missingDelimiterValue = "")
-    val className = fullQualifiedName.substringAfterLast('.')
-    return TestName(packageName, className, methodName)
-  }
-
-  private fun forcePlatformSdkVersion(compileSdkVersion: Int) {
-    val buildVersionClass = try {
-      Paparazzi::class.java.classLoader.loadClass("android.os.Build\$VERSION")
-    } catch (e: ClassNotFoundException) {
-      // Project unit tests don't load Android platform code
-      return
-    }
-    buildVersionClass
-      .getFieldReflectively("SDK_INT")
-      .setStaticValue(compileSdkVersion)
-  }
-
-  private fun initializeAppCompatIfPresent() {
-    lateinit var appCompatDelegateClass: Class<*>
-    try {
-      // See androidx.appcompat.widget.AppCompatDrawableManager#preload()
-      val appCompatDrawableManagerClass =
-        Class.forName("androidx.appcompat.widget.AppCompatDrawableManager")
-      val preloadMethod = appCompatDrawableManagerClass.getMethod("preload")
-      preloadMethod.invoke(null)
-
-      appCompatDelegateClass = Class.forName("androidx.appcompat.app.AppCompatDelegate")
-    } catch (e: ClassNotFoundException) {
-      logger.verbose("AppCompat not found on classpath")
-      return
-    }
-
-    // See androidx.appcompat.app.AppCompatDelegateImpl#installViewFactory()
-    if (layoutInflater.factory == null) {
-      layoutInflater.factory2 = object : LayoutInflater.Factory2 {
-        override fun onCreateView(
-          parent: View?,
-          name: String,
-          context: Context,
-          attrs: AttributeSet
-        ): View? {
-          val appCompatViewInflaterClass =
-            Class.forName("androidx.appcompat.app.AppCompatViewInflater")
-
-          val createViewMethod = appCompatViewInflaterClass
-            .getDeclaredMethod(
-              "createView",
-              View::class.java,
-              String::class.java,
-              Context::class.java,
-              AttributeSet::class.java,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType,
-              Boolean::class.javaPrimitiveType
-            )
-            .apply { isAccessible = true }
-
-          val inheritContext = true
-          val readAndroidTheme = true
-          val readAppTheme = true
-          val wrapContext = true
-
-          val newAppCompatViewInflaterInstance = appCompatViewInflaterClass
-            .getConstructor()
-            .newInstance()
-
-          return createViewMethod.invoke(
-            newAppCompatViewInflaterInstance, parent, name, context, attrs,
-            inheritContext, readAndroidTheme, readAppTheme, wrapContext
-          ) as View?
-        }
-
-        override fun onCreateView(
-          name: String,
-          context: Context,
-          attrs: AttributeSet
-        ): View? = onCreateView(null, name, context, attrs)
-      }
-    } else {
-      if (!appCompatDelegateClass.isAssignableFrom(layoutInflater.factory2::class.java)) {
-        throw IllegalStateException(
-          "The LayoutInflater already has a Factory installed so we can not install AppCompat's"
-        )
-      }
-    }
-  }
-
-  /**
-   * Current workaround for supporting custom fonts when constructing views in code. This check
-   * may be used or expanded to support other cases requiring similar method interception
-   * techniques.
-   *
-   * See:
-   * https://github.com/cashapp/paparazzi/issues/119
-   * https://issuetracker.google.com/issues/156065472
-   */
-  private fun registerFontLookupInterceptionIfResourceCompatDetected() {
-    try {
-      val resourcesCompatClass = Class.forName("androidx.core.content.res.ResourcesCompat")
-      InterceptorRegistrar.addMethodInterceptor(
-        resourcesCompatClass,
-        "getFont",
-        ResourcesInterceptor::class.java
-      )
-    } catch (e: ClassNotFoundException) {
-      logger.verbose("ResourceCompat not found on classpath")
-    }
-  }
-
-  private fun registerServiceManagerInterception() {
-    val serviceManager = Class.forName("android.os.ServiceManager")
-    InterceptorRegistrar.addMethodInterceptor(
-      serviceManager,
-      "getServiceOrThrow",
-      ServiceManagerInterceptor::class.java
-    )
-  }
-
-  private fun registerIInputMethodManagerInterception() {
-    val iimm = Class.forName("com.android.internal.view.IInputMethodManager\$Stub")
-    InterceptorRegistrar.addMethodInterceptor(
-      iimm,
-      "asInterface",
-      IInputMethodManagerInterceptor::class.java
-    )
-  }
-
-  private fun registerViewEditModeInterception() {
-    val viewClass = Class.forName("android.view.View")
-    InterceptorRegistrar.addMethodInterceptor(
-      viewClass,
-      "isInEditMode",
-      EditModeInterceptor::class.java
-    )
-  }
-
-  private fun registerMatrixMultiplyInterception() {
-    val matrixClass = Class.forName("android.opengl.Matrix")
-    InterceptorRegistrar.addMethodInterceptors(
-      matrixClass,
-      setOf(
-        "multiplyMM" to MatrixMatrixMultiplicationInterceptor::class.java,
-        "multiplyMV" to MatrixVectorMultiplicationInterceptor::class.java
-      )
-    )
-  }
-
-  private fun registerChoreographerDelegateInterception() {
-    val choreographerDelegateClass = Class.forName("android.view.Choreographer_Delegate")
-    InterceptorRegistrar.addMethodInterceptor(
-      choreographerDelegateClass,
-      "getFrameTimeNanos",
-      ChoreographerDelegateInterceptor::class.java
-    )
-  }
-
-  private fun forceReleaseComposeReferenceLeaks() {
-    // AndroidUiDispatcher is backed by a Handler, by executing one last time
-    // we give the dispatcher the ability to clean-up / release its callbacks.
-    executeHandlerCallbacks()
-  }
-
-  private fun executeHandlerCallbacks() {
-    // Avoid ConcurrentModificationException in
-    // RenderAction.currentContext.sessionInteractiveData.handlerMessageQueue.runnablesMap which is a WeakHashMap
-    // https://android.googlesource.com/platform/tools/adt/idea/+/c331c9b2f4334748c55c29adec3ad1cd67e45df2/designer/src/com/android/tools/idea/uibuilder/scene/LayoutlibSceneManager.java#1558
-    synchronized(this) {
-      // https://android.googlesource.com/platform/frameworks/layoutlib/+/d58aa4703369e109b24419548f38b422d5a44738/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java#171
-      // BridgeRenderSession.executeCallbacks aggressively tears down the main Looper and BridgeContext, so we call the static delegates ourselves.
-      Handler_Delegate.executeCallbacks()
-    }
-  }
-
-  private class PaparazziComposeOwner private constructor() :
-    LifecycleOwner, SavedStateRegistryOwner {
-    private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
-    private val savedStateRegistryController = SavedStateRegistryController.create(this)
-
-    override val lifecycle: Lifecycle
-      get() = lifecycleRegistry
-    override val savedStateRegistry: SavedStateRegistry =
-      savedStateRegistryController.savedStateRegistry
-
-    companion object {
-      fun register(view: View) {
-        val owner = PaparazziComposeOwner()
-        owner.savedStateRegistryController.performRestore(null)
-        owner.lifecycleRegistry.currentState = Lifecycle.State.CREATED
-        view.setViewTreeLifecycleOwner(owner)
-        view.setViewTreeSavedStateRegistryOwner(owner)
-      }
-    }
-  }
-
-  companion object {
-    /** The choreographer doesn't like 0 as a frame time, so start an hour later. */
-    internal val TIME_OFFSET_NANOS = TimeUnit.HOURS.toNanos(1L)
-
-    internal lateinit var renderer: Renderer
-    internal val isInitialized get() = ::renderer.isInitialized
-
-    internal lateinit var sessionParamsBuilder: SessionParamsBuilder
-
-    private val isVerifying: Boolean =
-      System.getProperty("paparazzi.test.verify")?.toBoolean() == true
-
-    private fun determineHandler(maxPercentDifference: Double): SnapshotHandler =
-      if (isVerifying) {
-        SnapshotVerifier(maxPercentDifference)
-      } else {
-        HtmlReportWriter()
-      }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt
deleted file mode 100644
index 15617f0..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package app.cash.paparazzi
-
-import java.lang.reflect.Field
-import java.lang.reflect.Modifier
-import java.security.PrivilegedAction
-import sun.misc.Unsafe
-
-/**
- * Inspired by and ported from:
- * https://github.com/powermock/powermock/commit/fc092c5d7e339d01e079184a2a0e88b5c46fc0e8
- * https://github.com/powermock/powermock/commit/bd92bcc5329c4981cf09dece5c3eafcf92fe49ff
- */
-internal fun Class<*>.getFieldReflectively(fieldName: String): Field =
-  try {
-    this.getDeclaredField(fieldName).also { it.isAccessible = true }
-  } catch (e: NoSuchFieldException) {
-    throw RuntimeException("Field '$fieldName' was not found in class $name.")
-  }
-
-internal fun Field.setStaticValue(value: Any) {
-  try {
-    this.isAccessible = true
-    val isFinalModifierPresent = this.modifiers and Modifier.FINAL == Modifier.FINAL
-    if (isFinalModifierPresent) {
-      @Suppress("DEPRECATION")
-      java.security.AccessController.doPrivileged<Any?>(
-        PrivilegedAction {
-          try {
-            val unsafe = Unsafe::class.java.getFieldReflectively("theUnsafe").get(null) as Unsafe
-            val offset = unsafe.staticFieldOffset(this)
-            val base = unsafe.staticFieldBase(this)
-            unsafe.setFieldValue(this, base, offset, value)
-            null
-          } catch (t: Throwable) {
-            throw RuntimeException(t)
-          }
-        }
-      )
-    } else {
-      this.set(null, value)
-    }
-  } catch (ex: SecurityException) {
-    throw RuntimeException(ex)
-  } catch (ex: IllegalAccessException) {
-    throw RuntimeException(ex)
-  } catch (ex: IllegalArgumentException) {
-    throw RuntimeException(ex)
-  }
-}
-
-internal fun Unsafe.setFieldValue(field: Field, base: Any, offset: Long, value: Any) =
-  when (field.type) {
-    Integer.TYPE -> this.putInt(base, offset, (value as Int))
-    java.lang.Short.TYPE -> this.putShort(base, offset, (value as Short))
-    java.lang.Long.TYPE -> this.putLong(base, offset, (value as Long))
-    java.lang.Byte.TYPE -> this.putByte(base, offset, (value as Byte))
-    java.lang.Boolean.TYPE -> this.putBoolean(base, offset, (value as Boolean))
-    java.lang.Float.TYPE -> this.putFloat(base, offset, (value as Float))
-    java.lang.Double.TYPE -> this.putDouble(base, offset, (value as Double))
-    Character.TYPE -> this.putChar(base, offset, (value as Char))
-    else -> this.putObject(base, offset, value)
-  }
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt
deleted file mode 100644
index d0d12b2..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/RenderExtension.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import android.view.View
-
-/**
- * An extension for overlaying additional information on top of each rendered frame.
- */
-interface RenderExtension {
-  /**
-   * Allows this extension to modify the view hierarchy represented by [contentView].
-   *
-   * Returns the root view of the modified hierarchy.
-   */
-  fun renderView(contentView: View): View
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
deleted file mode 100644
index 85501f7..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import com.squareup.moshi.JsonClass
-import java.util.Date
-import java.util.Locale
-
-@JsonClass(generateAdapter = true)
-data class Snapshot(
-  val name: String?,
-  val testName: TestName,
-  val timestamp: Date,
-  val tags: List<String> = listOf(),
-  val file: String? = null
-)
-
-internal fun Snapshot.toFileName(
-  delimiter: String = "_",
-  extension: String
-): String {
-  val formattedLabel = if (name != null) {
-    "$delimiter${name.lowercase(Locale.US).replace("\\s".toRegex(), delimiter)}"
-  } else {
-    ""
-  }
-  return "${testName.packageName}${delimiter}${testName.className}" +
-      "${delimiter}${testName.methodName}$formattedLabel.$extension"
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt
deleted file mode 100644
index 5067f25..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotHandler.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import java.awt.image.BufferedImage
-import java.io.Closeable
-
-interface SnapshotHandler : Closeable {
-  fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler
-
-  interface FrameHandler : Closeable {
-    fun handle(image: BufferedImage)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt
deleted file mode 100644
index 564cd2e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/SnapshotVerifier.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import app.cash.paparazzi.SnapshotHandler.FrameHandler
-import app.cash.paparazzi.internal.ImageUtils
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-
-class SnapshotVerifier @JvmOverloads constructor(
-  private val maxPercentDifference: Double,
-  rootDirectory: File = File("src/test/snapshots")
-) : SnapshotHandler {
-  private val imagesDirectory: File = File(rootDirectory, "images")
-  private val videosDirectory: File = File(rootDirectory, "videos")
-
-  init {
-    imagesDirectory.mkdirs()
-    videosDirectory.mkdirs()
-  }
-
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): FrameHandler {
-    return object : FrameHandler {
-      override fun handle(image: BufferedImage) {
-        // Note: does not handle videos or its frames at the moment
-        val expected = File(imagesDirectory, snapshot.toFileName(extension = "png"))
-        if (!expected.exists()) {
-          throw AssertionError("File $expected does not exist")
-        }
-
-        val goldenImage = ImageIO.read(expected)
-        ImageUtils.assertImageSimilar(
-          relativePath = expected.path,
-          image = image,
-          goldenImage = goldenImage,
-          maxPercentDifferent = maxPercentDifference
-        )
-      }
-
-      override fun close() = Unit
-    }
-  }
-
-  override fun close() = Unit
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt
deleted file mode 100644
index 2ab7963..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/TestName.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-data class TestName(
-  val packageName: String,
-  val className: String,
-  val methodName: String
-)
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt
deleted file mode 100644
index f6f16aa..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtension.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.accessibility
-
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.LayerDrawable
-import android.util.TypedValue
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.RenderExtension
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_DESCRIPTION_BACKGROUND_COLOR
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_RECT_SIZE
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_RENDER_ALPHA
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_TEXT_COLOR
-import app.cash.paparazzi.accessibility.RenderSettings.DEFAULT_TEXT_SIZE
-import app.cash.paparazzi.accessibility.RenderSettings.getColor
-import app.cash.paparazzi.accessibility.RenderSettings.toColorInt
-import app.cash.paparazzi.accessibility.RenderSettings.withAlpha
-
-class AccessibilityRenderExtension : RenderExtension {
-  override fun renderView(
-    contentView: View
-  ): View {
-    val accessibilityViews = contentView.findAccessibilityViews()
-    accessibilityViews.forEach { view ->
-      val color = getColor(view)
-      val colorInt = color.toColorInt()
-
-      val colorDrawable = GradientDrawable(
-        GradientDrawable.Orientation.TOP_BOTTOM,
-        intArrayOf(colorInt, colorInt)
-      ).apply {
-        setStroke(2, color.withAlpha(DEFAULT_RENDER_ALPHA * 2).toColorInt())
-      }
-
-      view.foreground = view.foreground?.let { drawable ->
-        // If there is an existing foreground layer the color on top of it.
-        LayerDrawable(arrayOf(drawable, colorDrawable))
-      } ?: colorDrawable
-    }
-
-    return LinearLayout(contentView.context).apply {
-      orientation = LinearLayout.HORIZONTAL
-      weightSum = 2f
-      layoutParams = ViewGroup.LayoutParams(
-        ViewGroup.LayoutParams.MATCH_PARENT,
-        ViewGroup.LayoutParams.MATCH_PARENT
-      )
-
-      val contentLayoutParams = contentView.layoutParams ?: generateLayoutParams(null)
-      addView(
-        contentView,
-        LinearLayout.LayoutParams(
-          contentLayoutParams.width,
-          contentLayoutParams.height,
-          1f
-        )
-      )
-      addView(
-        buildAccessibilityView(contentView),
-        LinearLayout.LayoutParams(
-          ViewGroup.LayoutParams.MATCH_PARENT,
-          ViewGroup.LayoutParams.MATCH_PARENT,
-          1f
-        )
-      )
-    }
-  }
-
-  private fun View.findAccessibilityViews(): List<View> {
-    val accessibilityViews = mutableListOf<View>()
-    if (isImportantForAccessibility && !iterableTextForAccessibility.isNullOrBlank()) {
-      accessibilityViews.add(this)
-    }
-
-    if (this is ViewGroup) {
-      (0 until childCount).forEach {
-        accessibilityViews += getChildAt(it).findAccessibilityViews()
-      }
-    }
-
-    return accessibilityViews
-  }
-
-  private fun buildAccessibilityView(contentView: View): View {
-    val linearLayout = LinearLayout(contentView.context).apply {
-      orientation = LinearLayout.VERTICAL
-      setBackgroundColor(DEFAULT_DESCRIPTION_BACKGROUND_COLOR.toColorInt())
-    }
-
-    fun renderAccessibility(view: View) {
-      if (view.isImportantForAccessibility && !view.iterableTextForAccessibility.isNullOrBlank()) {
-        linearLayout.addView(buildAccessibilityRow(view, view.iterableTextForAccessibility))
-      }
-
-      if (view is ViewGroup) {
-        (0 until view.childCount).forEach {
-          renderAccessibility(view.getChildAt(it))
-        }
-      }
-    }
-
-    renderAccessibility(contentView)
-    return linearLayout
-  }
-
-  private fun buildAccessibilityRow(view: View, iterableTextForAccessibility: CharSequence): View {
-    val context = view.context
-    val color = getColor(view).toColorInt()
-    val margin = view.dip(8)
-    val innerMargin = view.dip(4)
-
-    return LinearLayout(context).apply {
-      orientation = LinearLayout.HORIZONTAL
-      layoutParams = ViewGroup.LayoutParams(
-        ViewGroup.LayoutParams.MATCH_PARENT,
-        ViewGroup.LayoutParams.WRAP_CONTENT
-      )
-      setPaddingRelative(margin, innerMargin, margin, innerMargin)
-
-      addView(
-        View(context).apply {
-          layoutParams = ViewGroup.LayoutParams(dip(DEFAULT_RECT_SIZE), dip(DEFAULT_RECT_SIZE))
-          background = GradientDrawable(
-            GradientDrawable.Orientation.TOP_BOTTOM,
-            intArrayOf(color, color)
-          ).apply {
-            cornerRadius = dip(DEFAULT_RECT_SIZE / 4f)
-          }
-          setPaddingRelative(innerMargin, innerMargin, innerMargin, innerMargin)
-        }
-      )
-      addView(
-        TextView(context).apply {
-          layoutParams = ViewGroup.LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          )
-          text = iterableTextForAccessibility
-          textSize = DEFAULT_TEXT_SIZE
-          setTextColor(DEFAULT_TEXT_COLOR.toColorInt())
-          setPaddingRelative(innerMargin, 0, innerMargin, 0)
-        }
-      )
-    }
-  }
-}
-
-private fun View.dip(value: Float): Float =
-  TypedValue.applyDimension(
-    TypedValue.COMPLEX_UNIT_DIP,
-    value,
-    resources.displayMetrics
-  )
-
-private fun View.dip(value: Int): Int = dip(value.toFloat()).toInt()
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt
deleted file mode 100644
index 768fc6e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/accessibility/RenderSettings.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.accessibility
-
-import android.view.View
-import java.awt.Color
-
-internal object RenderSettings {
-  const val DEFAULT_RENDER_ALPHA = 40
-  val DEFAULT_RENDER_COLORS = listOf(
-    Color.RED,
-    Color.GREEN,
-    Color.BLUE,
-    Color.YELLOW,
-    Color.ORANGE,
-    Color.MAGENTA,
-    Color.CYAN,
-    Color.PINK
-  )
-  val DEFAULT_TEXT_COLOR: Color = Color.BLACK
-  val DEFAULT_DESCRIPTION_BACKGROUND_COLOR: Color = Color.WHITE
-  const val DEFAULT_TEXT_SIZE: Float = 10f
-  const val DEFAULT_RECT_SIZE: Int = 16
-
-  private val colorMap = mutableMapOf<Int, Color>()
-
-  fun getColor(view: View): Color {
-    val key = "${view::class.simpleName}(${view.iterableTextForAccessibility})"
-    return getColor(key)
-  }
-
-  private fun getColor(key: String): Color {
-    val hashCode = key.hashCode()
-    return colorMap.getOrPut(hashCode) {
-      nextColor(hashCode).withAlpha(DEFAULT_RENDER_ALPHA)
-    }
-  }
-
-  private fun nextColor(hashCode: Int): Color {
-    return DEFAULT_RENDER_COLORS[colorIndex(hashCode)]
-  }
-
-  private fun colorIndex(hashCode: Int): Int {
-    val size = DEFAULT_RENDER_COLORS.size
-    val i = hashCode % size
-    return if (i < 0) i + size else i
-  }
-
-  internal fun Color.toColorInt(): Int =
-    android.graphics.Color.argb(alpha, red, green, blue)
-
-  internal fun Color.withAlpha(alpha: Int): Color {
-    return Color(red, green, blue, alpha)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt
deleted file mode 100644
index 57b7bf1..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ChoreographerDelegateInterceptor.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.view.Choreographer
-import com.android.internal.lang.System_Delegate
-
-object ChoreographerDelegateInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    @Suppress("UNUSED_PARAMETER") choreographer: Choreographer
-  ): Long = System_Delegate.nanoTime()
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt
deleted file mode 100644
index c464a04..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/EditModeInterceptor.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package app.cash.paparazzi.internal
-
-object EditModeInterceptor {
-  @JvmStatic
-  fun intercept(): Boolean = false
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt
deleted file mode 100644
index 5bbd25c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Gc.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import java.lang.ref.WeakReference
-
-internal object Gc {
-  fun gc() {
-    // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
-    var obj: Any? = Any()
-    val ref = WeakReference<Any>(obj)
-
-    @Suppress("UNUSED_VAlUE")
-    obj = null
-    while (ref.get() != null) {
-      System.gc()
-      System.runFinalization()
-    }
-
-    System.gc()
-    System.runFinalization()
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt
deleted file mode 100644
index 1908949..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/IInputMethodManagerInterceptor.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.os.IBinder
-import com.android.internal.view.IInputMethodManager
-
-/**
- * With [ServiceManagerInterceptor] returning null for the service, we must override the logic
- * in [com.android.internal.view.IInputMethodManager.Stub.asInterface] to return the default
- * implementation of [IInputMethodManager].
- */
-object IInputMethodManagerInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun interceptAsInterface(@Suppress("UNUSED_PARAMETER") obj: IBinder?): IInputMethodManager =
-    IInputMethodManager.Default()
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt
deleted file mode 100644
index 8e7111c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ImageUtils.kt
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import java.awt.AlphaComposite
-import java.awt.Color
-import java.awt.Graphics2D
-import java.awt.RenderingHints.KEY_ANTIALIASING
-import java.awt.RenderingHints.KEY_INTERPOLATION
-import java.awt.RenderingHints.KEY_RENDERING
-import java.awt.RenderingHints.VALUE_ANTIALIAS_ON
-import java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR
-import java.awt.RenderingHints.VALUE_RENDER_QUALITY
-import java.awt.image.BufferedImage
-import java.awt.image.BufferedImage.TYPE_INT_ARGB
-import java.io.File
-import java.io.File.separatorChar
-import java.io.IOException
-import javax.imageio.ImageIO
-import kotlin.math.max
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-
-/**
- * Utilities related to image processing.
- */
-internal object ImageUtils {
-  /**
-   * Normally, this test will fail when there is a missing thumbnail. However, when
-   * you create creating a new test, it's useful to be able to turn this off such that
-   * you can generate all the missing thumbnails in one go, rather than having to run
-   * the test repeatedly to get to each new render assertion generating its thumbnail.
-   */
-  private val FAIL_ON_MISSING_THUMBNAIL = true
-
-  private const val THUMBNAIL_SIZE = 1000
-
-  /** Directory where to write the thumbnails and deltas. */
-  private val failureDir: File
-    get() {
-      val workingDirString = System.getProperty("user.dir")
-      val failureDir = File(workingDirString, "out/failures")
-      failureDir.mkdirs()
-      return failureDir
-    }
-
-  @Throws(IOException::class)
-  fun requireSimilar(
-    relativePath: String,
-    image: BufferedImage,
-    maxPercentDifference: Double
-  ) {
-    val scale = getThumbnailScale(image)
-    val thumbnail = scale(image, scale, scale)
-
-    val `is` = ImageUtils::class.java.classLoader.getResourceAsStream(relativePath)
-    if (`is` ==
-      null
-    ) {
-      var message = "Unable to load golden thumbnail: $relativePath\n"
-      message = saveImageAndAppendMessage(thumbnail, message, relativePath)
-      if (FAIL_ON_MISSING_THUMBNAIL) {
-        fail(message)
-      } else {
-        println(message)
-      }
-    } else {
-      try {
-        val goldenImage = ImageIO.read(`is`)
-        assertImageSimilar(
-          relativePath,
-          goldenImage,
-          thumbnail,
-          maxPercentDifference
-        )
-      } finally {
-        `is`.close()
-      }
-    }
-  }
-
-  @Throws(IOException::class)
-  fun assertImageSimilar(
-    relativePath: String,
-    goldenImage: BufferedImage,
-    image: BufferedImage,
-    maxPercentDifferent: Double
-  ) {
-    @Suppress("NAME_SHADOWING") var goldenImage = goldenImage
-    if (goldenImage.type != TYPE_INT_ARGB) {
-      val temp = BufferedImage(
-        goldenImage.width,
-        goldenImage.height,
-        TYPE_INT_ARGB
-      )
-      temp.graphics.drawImage(goldenImage, 0, 0, null)
-      goldenImage = temp
-    }
-    assertEquals(TYPE_INT_ARGB.toLong(), goldenImage.type.toLong())
-
-    val imageWidth = Math.min(goldenImage.width, image.width)
-    val imageHeight = Math.min(goldenImage.height, image.height)
-
-    // Blur the images to account for the scenarios where there are pixel
-    // differences
-    // in where a sharp edge occurs
-    // goldenImage = blur(goldenImage, 6);
-    // image = blur(image, 6);
-
-    val width = 3 * imageWidth
-    val deltaImage = BufferedImage(width, imageHeight, TYPE_INT_ARGB)
-    val g = deltaImage.graphics
-
-    // Compute delta map
-    var delta: Long = 0
-    for (y in 0 until imageHeight) {
-      for (x in 0 until imageWidth) {
-        val goldenRgb = goldenImage.getRGB(x, y)
-        val rgb = image.getRGB(x, y)
-        if (goldenRgb == rgb) {
-          deltaImage.setRGB(imageWidth + x, y, 0x00808080)
-          continue
-        }
-
-        // If the pixels have no opacity, don't delta colors at all
-        if (goldenRgb and -0x1000000 == 0 && rgb and -0x1000000 == 0) {
-          deltaImage.setRGB(imageWidth + x, y, 0x00808080)
-          continue
-        }
-
-        val deltaR = (rgb and 0xFF0000).ushr(16) - (goldenRgb and 0xFF0000).ushr(16)
-        val newR = 128 + deltaR and 0xFF
-        val deltaG = (rgb and 0x00FF00).ushr(8) - (goldenRgb and 0x00FF00).ushr(8)
-        val newG = 128 + deltaG and 0xFF
-        val deltaB = (rgb and 0x0000FF) - (goldenRgb and 0x0000FF)
-        val newB = 128 + deltaB and 0xFF
-
-        val avgAlpha =
-          ((goldenRgb and -0x1000000).ushr(24) + (rgb and -0x1000000).ushr(24)) / 2 shl 24
-
-        val newRGB = avgAlpha or (newR shl 16) or (newG shl 8) or newB
-        deltaImage.setRGB(imageWidth + x, y, newRGB)
-
-        delta += Math.abs(deltaR)
-          .toLong()
-        delta += Math.abs(deltaG)
-          .toLong()
-        delta += Math.abs(deltaB)
-          .toLong()
-      }
-    }
-
-    // 3 different colors, 256 color levels
-    val total = imageHeight.toLong() * imageWidth.toLong() * 3L * 256L
-    val percentDifference = (delta * 100 / total.toDouble()).toFloat()
-
-    var error: String? = null
-    val imageName = getName(relativePath)
-    if (percentDifference > maxPercentDifferent) {
-      error = String.format("Images differ (by %.1f%%)", percentDifference)
-    } else if (Math.abs(goldenImage.width - image.width) >= 2) {
-      error = "Widths differ too much for " + imageName + ": " +
-        goldenImage.width + "x" + goldenImage.height +
-        "vs" + image.width + "x" + image.height
-    } else if (Math.abs(goldenImage.height - image.height) >= 2) {
-      error = "Heights differ too much for " + imageName + ": " +
-        goldenImage.width + "x" + goldenImage.height +
-        "vs" + image.width + "x" + image.height
-    }
-
-    if (error != null) {
-      // Expected on the left
-      // Golden on the right
-      g.drawImage(goldenImage, 0, 0, null)
-      g.drawImage(image, 2 * imageWidth, 0, null)
-
-      // Labels
-      if (imageWidth > 80) {
-        g.color = Color.RED
-        g.drawString("Expected", 10, 20)
-        g.drawString("Actual", 2 * imageWidth + 10, 20)
-      }
-
-      val output = File(failureDir, "delta-$imageName")
-      if (output.exists()) {
-        val deleted = output.delete()
-        assertTrue(deleted)
-      }
-      ImageIO.write(deltaImage, "PNG", output)
-      error += " - see details in file://" + output.path + "\n"
-      error = saveImageAndAppendMessage(image, error, relativePath)
-      println(error)
-      fail(error)
-    }
-
-    g.dispose()
-  }
-
-  /**
-   * Resize the given image
-   *
-   * @param source the image to be scaled
-   * @param xScale x scale
-   * @param yScale y scale
-   * @return the scaled image
-   */
-  fun scale(
-    source: BufferedImage,
-    xScale: Double,
-    yScale: Double
-  ): BufferedImage {
-    @Suppress("NAME_SHADOWING") var source = source
-
-    var sourceWidth = source.width
-    var sourceHeight = source.height
-    val destWidth = Math.max(1, (xScale * sourceWidth).toInt())
-    val destHeight = Math.max(1, (yScale * sourceHeight).toInt())
-    var imageType = source.type
-    if (imageType == BufferedImage.TYPE_CUSTOM) {
-      imageType = BufferedImage.TYPE_INT_ARGB
-    }
-    if (xScale > 0.5 && yScale > 0.5) {
-      val scaled = BufferedImage(destWidth, destHeight, imageType)
-      val g2 = scaled.createGraphics()
-      g2.composite = AlphaComposite.Src
-      g2.color = Color(0, true)
-      g2.fillRect(0, 0, destWidth, destHeight)
-      if (xScale == 1.0 && yScale == 1.0) {
-        g2.drawImage(source, 0, 0, null)
-      } else {
-        setRenderingHints(g2)
-        g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight, null)
-      }
-      g2.dispose()
-      return scaled
-    } else {
-      // When creating a thumbnail, using the above code doesn't work very well;
-      // you get some visible artifacts, especially for text. Instead use the
-      // technique of repeatedly scaling the image into half; this will cause
-      // proper averaging of neighboring pixels, and will typically (for the kinds
-      // of screen sizes used by this utility method in the layout editor) take
-      // about 3-4 iterations to get the result since we are logarithmically reducing
-      // the size. Besides, each successive pass in operating on much fewer pixels
-      // (a reduction of 4 in each pass).
-      //
-      // However, we may not be resizing to a size that can be reached exactly by
-      // successively diving in half. Therefore, once we're within a factor of 2 of
-      // the final size, we can do a resize to the exact target size.
-      // However, we can get even better results if we perform this final resize
-      // up front. Let's say we're going from width 1000 to a destination width of 85.
-      // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
-      // then a resize from 125 to 85. That last resize can distort/blur a lot.
-      // Instead, we can start with the destination width, 85, and double it
-      // successfully until we're close to the initial size: 85, then 170,
-      // then 340, and finally 680. (The next one, 1360, is larger than 1000).
-      // So, now we *start* the thumbnail operation by resizing from width 1000 to
-      // width 680, which will preserve a lot of visual details such as text.
-      // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
-      // We end up with the expected final size, but we've been doing an exact
-      // divide-in-half resizing operation at the end so there is less distortion.
-
-      var iterations = 0 // Number of halving operations to perform after the initial resize
-      var nearestWidth = destWidth // Width closest to source width that = 2^x, x is integer
-      var nearestHeight = destHeight
-      while (nearestWidth < sourceWidth / 2) {
-        nearestWidth *= 2
-        nearestHeight *= 2
-        iterations++
-      }
-
-      var scaled = BufferedImage(nearestWidth, nearestHeight, imageType)
-
-      var g2 = scaled.createGraphics()
-      setRenderingHints(g2)
-      g2.drawImage(source, 0, 0, nearestWidth, nearestHeight, 0, 0, sourceWidth, sourceHeight, null)
-      g2.dispose()
-
-      sourceWidth = nearestWidth
-      sourceHeight = nearestHeight
-      source = scaled
-
-      for (iteration in iterations - 1 downTo 0) {
-        val halfWidth = sourceWidth / 2
-        val halfHeight = sourceHeight / 2
-        scaled = BufferedImage(halfWidth, halfHeight, imageType)
-        g2 = scaled.createGraphics()
-        setRenderingHints(g2)
-        g2.drawImage(source, 0, 0, halfWidth, halfHeight, 0, 0, sourceWidth, sourceHeight, null)
-        g2.dispose()
-
-        sourceWidth = halfWidth
-        sourceHeight = halfHeight
-        source = scaled
-        iterations--
-      }
-      return scaled
-    }
-  }
-
-  fun getThumbnailScale(image: BufferedImage): Double {
-    val maxDimension = max(image.width, image.height)
-    return THUMBNAIL_SIZE / maxDimension.toDouble()
-  }
-
-  private fun setRenderingHints(g2: Graphics2D) {
-    g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR)
-    g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY)
-    g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)
-  }
-
-  /**
-   * Saves the generated thumbnail image and appends the info message to an initial message
-   */
-  @Throws(IOException::class)
-  private fun saveImageAndAppendMessage(
-    image: BufferedImage,
-    initialMessage: String,
-    relativePath: String
-  ): String {
-    @Suppress("NAME_SHADOWING") var initialMessage = initialMessage
-    val output = File(
-      failureDir,
-      getName(relativePath)
-    )
-    if (output.exists()) {
-      val deleted = output.delete()
-      assertTrue(deleted)
-    }
-    ImageIO.write(image, "PNG", output)
-    initialMessage += "Thumbnail for current rendering stored at file://" + output.path
-    //        initialMessage += "\nRun the following command to accept the changes:\n";
-    //        initialMessage += String.format("mv %1$s %2$s", output.getPath(),
-    //                ImageUtils.class.getResource(relativePath).getPath());
-    // The above has been commented out, since the destination path returned is in out dir
-    // and it makes the tests pass without the code being actually checked in.
-    return initialMessage
-  }
-
-  private fun getName(relativePath: String): String {
-    return relativePath.substring(relativePath.lastIndexOf(separatorChar) + 1)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt
deleted file mode 100644
index 41994c2d..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixMatrixMultiplicationInterceptor.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package app.cash.paparazzi.internal
-
-// Sampled from https://cs.android.com/android/platform/superproject/+/master:external/robolectric-shadows/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOpenGLMatrix.java;l=10-67
-object MatrixMatrixMultiplicationInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    result: FloatArray,
-    resultOffset: Int,
-    lhs: FloatArray,
-    lhsOffset: Int,
-    rhs: FloatArray,
-    rhsOffset: Int
-  ) {
-    require(resultOffset + 16 <= result.size) { "resultOffset + 16 > result.length" }
-    require(lhsOffset + 16 <= lhs.size) { "lhsOffset + 16 > lhs.length" }
-    require(rhsOffset + 16 <= rhs.size) { "rhsOffset + 16 > rhs.length" }
-    for (i in 0..3) {
-      val rhs_i0 = rhs[I(i, 0, rhsOffset)]
-      var ri0 = lhs[I(0, 0, lhsOffset)] * rhs_i0
-      var ri1 = lhs[I(0, 1, lhsOffset)] * rhs_i0
-      var ri2 = lhs[I(0, 2, lhsOffset)] * rhs_i0
-      var ri3 = lhs[I(0, 3, lhsOffset)] * rhs_i0
-      for (j in 1..3) {
-        val rhs_ij = rhs[I(i, j, rhsOffset)]
-        ri0 += lhs[I(j, 0, lhsOffset)] * rhs_ij
-        ri1 += lhs[I(j, 1, lhsOffset)] * rhs_ij
-        ri2 += lhs[I(j, 2, lhsOffset)] * rhs_ij
-        ri3 += lhs[I(j, 3, lhsOffset)] * rhs_ij
-      }
-      result[I(i, 0, resultOffset)] = ri0
-      result[I(i, 1, resultOffset)] = ri1
-      result[I(i, 2, resultOffset)] = ri2
-      result[I(i, 3, resultOffset)] = ri3
-    }
-  }
-
-  @Suppress("FunctionName")
-  private fun I(i: Int, j: Int, offset: Int): Int {
-    // #define I(_i, _j) ((_j)+ 4*(_i))
-    return offset + j + 4 * i
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt
deleted file mode 100644
index 403413e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/MatrixVectorMultiplicationInterceptor.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package app.cash.paparazzi.internal
-
-// Sampled from https://cs.android.com/android/platform/superproject/+/master:external/robolectric-shadows/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOpenGLMatrix.java;l=69-121
-object MatrixVectorMultiplicationInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun intercept(
-    resultVec: FloatArray,
-    resultVecOffset: Int,
-    lhsMat: FloatArray,
-    lhsMatOffset: Int,
-    rhsVec: FloatArray,
-    rhsVecOffset: Int
-  ) {
-    require(resultVecOffset + 4 <= resultVec.size) { "resultOffset + 4 > result.length" }
-    require(lhsMatOffset + 16 <= lhsMat.size) { "lhsOffset + 16 > lhs.length" }
-    require(rhsVecOffset + 4 <= rhsVec.size) { "rhsOffset + 4 > rhs.length" }
-    val x = rhsVec[rhsVecOffset + 0]
-    val y = rhsVec[rhsVecOffset + 1]
-    val z = rhsVec[rhsVecOffset + 2]
-    val w = rhsVec[rhsVecOffset + 3]
-    resultVec[resultVecOffset + 0] =
-      lhsMat[I(0, 0, lhsMatOffset)] * x + lhsMat[I(1, 0, lhsMatOffset)] * y +
-          lhsMat[I(2, 0, lhsMatOffset)] * z + lhsMat[I(3, 0, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 1] =
-      lhsMat[I(0, 1, lhsMatOffset)] * x + lhsMat[I(1, 1, lhsMatOffset)] * y +
-          lhsMat[I(2, 1, lhsMatOffset)] * z + lhsMat[I(3, 1, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 2] =
-      lhsMat[I(0, 2, lhsMatOffset)] * x + lhsMat[I(1, 2, lhsMatOffset)] * y +
-          lhsMat[I(2, 2, lhsMatOffset)] * z + lhsMat[I(3, 2, lhsMatOffset)] * w
-    resultVec[resultVecOffset + 3] =
-      lhsMat[I(0, 3, lhsMatOffset)] * x + lhsMat[I(1, 3, lhsMatOffset)] * y +
-          lhsMat[I(2, 3, lhsMatOffset)] * z + lhsMat[I(3, 3, lhsMatOffset)] * w
-  }
-
-  @Suppress("FunctionName")
-  private fun I(i: Int, j: Int, offset: Int): Int {
-    // #define I(_i, _j) ((_j)+ 4*(_i))
-    return offset + j + 4 * i
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt
deleted file mode 100644
index 72bcf85..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziAssetRepository.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import com.android.ide.common.rendering.api.AssetRepository
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.io.InputStream
-
-internal class PaparazziAssetRepository(private val assetPath: String) : AssetRepository() {
-  @Throws(FileNotFoundException::class)
-  private fun open(path: String): InputStream? {
-    val asset = File(path)
-    return when {
-      asset.isFile -> FileInputStream(asset)
-      else -> null
-    }
-  }
-
-  override fun isSupported(): Boolean = true
-
-  @Throws(IOException::class)
-  override fun openAsset(
-    path: String,
-    mode: Int
-  ): InputStream? = open("$assetPath/$path")
-
-  @Throws(IOException::class)
-  override fun openNonAsset(
-    cookie: Int,
-    path: String,
-    mode: Int
-  ): InputStream? = open(path)
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt
deleted file mode 100644
index 6e2b62e..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziCallback.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import app.cash.paparazzi.internal.parsers.TagSnapshot
-import com.android.ide.common.rendering.api.ActionBarCallback
-import com.android.ide.common.rendering.api.AdapterBinding
-import com.android.ide.common.rendering.api.ILayoutPullParser
-import com.android.ide.common.rendering.api.LayoutlibCallback
-import com.android.ide.common.rendering.api.ResourceNamespace.RES_AUTO
-import com.android.ide.common.rendering.api.ResourceReference
-import com.android.ide.common.rendering.api.ResourceValue
-import com.android.ide.common.rendering.api.SessionParams.Key
-import com.android.layoutlib.bridge.android.RenderParamsFlags
-import com.android.resources.ResourceType
-import com.android.resources.ResourceType.STYLE
-import com.google.common.io.ByteStreams
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.lang.reflect.Modifier
-import org.kxml2.io.KXmlParser
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-
-internal class PaparazziCallback(
-  private val logger: PaparazziLogger,
-  private val resourcePackageNames: List<String>
-) : LayoutlibCallback() {
-  private val projectResources = mutableMapOf<Int, ResourceReference>()
-  private val resources = mutableMapOf<ResourceReference, Int>()
-  private val actionBarCallback = ActionBarCallback()
-  private val aaptDeclaredResources = mutableMapOf<String, TagSnapshot>()
-
-  private var adaptiveIconMaskPath: String? = null
-  private val loadedClasses = mutableMapOf<String, Class<*>>()
-
-  @Throws(ClassNotFoundException::class)
-  fun initResources() {
-    for (rPackageName in resourcePackageNames) {
-      val rClass = Class.forName("$rPackageName.R")
-      for (resourceClass in rClass.declaredClasses) {
-        val resourceType = ResourceType.fromClassName(resourceClass.simpleName) ?: continue
-
-        for (field in resourceClass.declaredFields) {
-          if (!Modifier.isStatic(field.modifiers)) continue
-
-          // May not be final in library projects.
-          val type = field.type
-          try {
-            if (type == Int::class.javaPrimitiveType) {
-              val value = field.get(null) as Int
-              val reference = ResourceReference(RES_AUTO, resourceType, field.name)
-              projectResources[value] = reference
-              resources[reference] = value
-            } else if (type.isArray && type.componentType == Int::class.javaPrimitiveType) {
-              // Ignore.
-            } else {
-              logger.error(null, "Unknown field type in R class: $type")
-            }
-          } catch (e: IllegalAccessException) {
-            logger.error(e, "Malformed R class: %1\$s", "$rPackageName.R")
-          }
-        }
-      }
-    }
-  }
-
-  @Throws(Exception::class)
-  override fun loadView(
-    name: String,
-    constructorSignature: Array<Class<*>>,
-    constructorArgs: Array<Any>
-  ): Any? {
-    val viewClass = Class.forName(name)
-    val viewConstructor = viewClass.getConstructor(*constructorSignature)
-    viewConstructor.isAccessible = true
-    return viewConstructor.newInstance(*constructorArgs)
-  }
-
-  override fun resolveResourceId(id: Int): ResourceReference? = projectResources[id]
-
-  override fun getOrGenerateResourceId(resource: ResourceReference): Int {
-    // Workaround: We load our resource map from fields in R.class, which are named using Java
-    // class conventions.  Therefore, we need to similarly transform style naming conventions
-    // that contain periods (e.g., Widget.AppCompat.TextView) to avoid false lookup misses.
-    // Long-term: Perhaps parse and load resource names from file system directly?
-    val resourceKey =
-      if (resource.resourceType == STYLE) resource.transformStyleResource() else resource
-    return resources[resourceKey] ?: 0
-  }
-
-  override fun getParser(layoutResource: ResourceValue): ILayoutPullParser? {
-    try {
-      val value = layoutResource.value ?: return null
-      if (aaptDeclaredResources.isNotEmpty() && layoutResource.resourceType == ResourceType.AAPT) {
-        val aaptResource = aaptDeclaredResources.getValue(value)
-        return LayoutPullParser.createFromAaptResource(aaptResource)
-      }
-
-      return LayoutPullParser.createFromFile(File(layoutResource.value))
-        .also {
-          // For parser of elements included in this parser, publish any aapt declared values
-          aaptDeclaredResources.putAll(it.getAaptDeclaredAttrs())
-        }
-    } catch (e: FileNotFoundException) {
-      return null
-    }
-  }
-
-  override fun getAdapterItemValue(
-    adapterView: ResourceReference,
-    adapterCookie: Any,
-    itemRef: ResourceReference,
-    fullPosition: Int,
-    positionPerType: Int,
-    fullParentPosition: Int,
-    parentPositionPerType: Int,
-    viewRef: ResourceReference,
-    viewAttribute: ViewAttribute,
-    defaultValue: Any
-  ): Any? = null
-
-  override fun getAdapterBinding(
-    adapterViewRef: ResourceReference,
-    adapterCookie: Any,
-    viewObject: Any
-  ): AdapterBinding? = null
-
-  override fun getActionBarCallback(): ActionBarCallback = actionBarCallback
-
-  override fun createXmlParserForPsiFile(fileName: String): XmlPullParser? =
-    createXmlParserForFile(fileName)
-
-  override fun createXmlParserForFile(fileName: String): XmlPullParser? {
-    try {
-      FileInputStream(fileName).use { fileStream ->
-        // Read data fully to memory to be able to close the file stream.
-        val byteOutputStream = ByteArrayOutputStream()
-        ByteStreams.copy(fileStream, byteOutputStream)
-        val parser = KXmlParser()
-        parser.setInput(ByteArrayInputStream(byteOutputStream.toByteArray()), null)
-        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
-        return parser
-      }
-    } catch (e: IOException) {
-      return null
-    } catch (e: XmlPullParserException) {
-      return null
-    }
-  }
-
-  override fun createXmlParser(): XmlPullParser = KXmlParser()
-
-  @Suppress("UNCHECKED_CAST")
-  override fun <T> getFlag(key: Key<T>?): T? {
-    return when (key) {
-      RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH -> adaptiveIconMaskPath as T?
-      else -> null
-    }
-  }
-
-  fun setAdaptiveIconMaskPath(adaptiveIconMaskPath: String) {
-    this.adaptiveIconMaskPath = adaptiveIconMaskPath
-  }
-
-  override fun findClass(name: String): Class<*> {
-    val clazz = loadedClasses[name]
-    logger.verbose("loadClassA($name)")
-
-    try {
-      if (clazz != null) {
-        return clazz
-      }
-      val clazz2 = Class.forName(name)
-      logger.verbose("loadClassB($name)")
-      loadedClasses[name] = clazz2
-      return clazz2
-    } catch (e: LinkageError) {
-      throw ClassNotFoundException("error loading class $name", e)
-    } catch (e: ExceptionInInitializerError) {
-      throw ClassNotFoundException("error loading class $name", e)
-    } catch (e: ClassNotFoundException) {
-      throw ClassNotFoundException("error loading class $name", e)
-    }
-  }
-
-  private fun ResourceReference.transformStyleResource() =
-    ResourceReference.style(namespace, name.replace('.', '_'))
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt
deleted file mode 100644
index 6739e81..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziJson.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.TestName
-import com.squareup.moshi.FromJson
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.ToJson
-import com.squareup.moshi.Types
-import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
-import java.util.Date
-
-internal object PaparazziJson {
-  val moshi = Moshi.Builder()
-    .add(Date::class.java, Rfc3339DateJsonAdapter())
-    .add(this)
-    .build()!!
-
-  val listOfShotsAdapter: JsonAdapter<List<Snapshot>> =
-    moshi
-      .adapter<List<Snapshot>>(
-        Types.newParameterizedType(List::class.java, Snapshot::class.java)
-      )
-      .indent("  ")
-
-  val listOfStringsAdapter: JsonAdapter<List<String>> =
-    moshi
-      .adapter<List<String>>(
-        Types.newParameterizedType(List::class.java, String::class.java)
-      )
-      .indent("  ")
-
-  @ToJson
-  fun testNameToJson(testName: TestName): String {
-    return "${testName.packageName}.${testName.className}#${testName.methodName}"
-  }
-
-  @FromJson
-  fun testNameFromJson(json: String): TestName {
-    val regex = Regex("(.*)\\.([^.]*)#([^.]*)")
-    val (packageName, className, methodName) = regex.matchEntire(json)!!.destructured
-    return TestName(packageName, className, methodName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt
deleted file mode 100644
index 01595f7..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/PaparazziLogger.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.Paparazzi
-import com.android.ide.common.rendering.api.ILayoutLog
-import com.android.utils.ILogger
-import java.io.PrintStream
-import java.io.PrintWriter
-import java.util.logging.Level
-import java.util.logging.Logger
-import java.util.logging.Logger.getLogger
-
-/**
- * This logger delegates to java.util.Logging.
- */
-internal class PaparazziLogger : ILayoutLog, ILogger {
-  private val logger: Logger = getLogger(Paparazzi::class.java.name)
-  private val errors = mutableListOf<Throwable>()
-
-  override fun error(
-    throwable: Throwable?,
-    format: String?,
-    vararg args: Any
-  ) {
-    logger.log(Level.SEVERE, format?.format(args), throwable)
-    if (throwable != null) {
-      errors += throwable
-    }
-  }
-
-  override fun warning(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.WARNING, format, args)
-  }
-
-  override fun info(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.INFO, format, args)
-  }
-
-  override fun verbose(
-    format: String,
-    vararg args: Any
-  ) {
-    logger.log(Level.FINE, format, args)
-  }
-
-  override fun fidelityWarning(
-    tag: String?,
-    message: String?,
-    throwable: Throwable?,
-    cookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.WARNING, "$tag: $message", throwable)
-  }
-
-  override fun error(
-    tag: String?,
-    message: String?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.SEVERE, "$tag: $message")
-  }
-
-  override fun error(
-    tag: String?,
-    message: String?,
-    throwable: Throwable?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.SEVERE, "$tag: $message", throwable)
-    if (throwable != null) {
-      errors += throwable
-    }
-  }
-
-  override fun warning(
-    tag: String?,
-    message: String?,
-    viewCookie: Any?,
-    data: Any?
-  ) {
-    logger.log(Level.WARNING, "$tag: $message")
-  }
-
-  override fun logAndroidFramework(priority: Int, tag: String?, message: String?) {
-    logger.log(Level.INFO, "$tag [$priority]: $message")
-  }
-
-  fun assertNoErrors() {
-    when (errors.size) {
-      0 -> return
-      1 -> throw errors[0]
-      else -> throw MultipleFailuresException(errors)
-    }
-  }
-
-  internal class MultipleFailuresException(private val causes: List<Throwable>) : Exception() {
-    init {
-      require(causes.isNotEmpty()) { "List of Throwables must not be empty" }
-    }
-
-    override val message: String
-      get() = buildString {
-        appendLine(String.format("There were %d errors:", causes.size))
-        causes.forEach { e ->
-          appendLine(String.format("%n  %s: %s", e.javaClass.name, e.message))
-          e.stackTrace.forEach { traceElement ->
-            appendLine("\tat $traceElement")
-          }
-        }
-      }
-
-    override fun printStackTrace() {
-      causes.forEach { e ->
-        e.printStackTrace()
-      }
-    }
-
-    override fun printStackTrace(s: PrintStream) {
-      causes.forEach { e ->
-        e.printStackTrace(s)
-      }
-    }
-
-    override fun printStackTrace(s: PrintWriter) {
-      causes.forEach { e ->
-        e.printStackTrace(s)
-      }
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt
deleted file mode 100644
index cbf05df..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/RenderResult.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import com.android.ide.common.rendering.api.RenderSession
-import com.android.ide.common.rendering.api.Result
-import com.android.ide.common.rendering.api.ViewInfo
-import java.awt.image.BufferedImage
-
-internal data class RenderResult(
-  val result: Result,
-  val systemViews: List<ViewInfo>,
-  val rootViews: List<ViewInfo>,
-  val image: BufferedImage
-)
-
-internal fun RenderSession.toResult(): RenderResult {
-  return RenderResult(result, systemRootViews.toList(), rootViews.toList(), image)
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt
deleted file mode 100644
index 569d1cd..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/Renderer.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Environment
-import app.cash.paparazzi.Flags
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.io.FolderWrapper
-import com.android.layoutlib.bridge.Bridge
-import com.android.layoutlib.bridge.android.RenderParamsFlags
-import com.android.layoutlib.bridge.impl.DelegateManager
-import java.awt.image.BufferedImage
-import java.io.Closeable
-import java.io.File
-import java.io.IOException
-import java.util.Locale
-
-/** View rendering. */
-internal class Renderer(
-  private val environment: Environment,
-  private val layoutlibCallback: PaparazziCallback,
-  private val logger: PaparazziLogger,
-  private val maxPercentDifference: Double
-) : Closeable {
-  private var bridge: Bridge? = null
-  private lateinit var sessionParamsBuilder: SessionParamsBuilder
-
-  /** Initialize the bridge and the resource maps. */
-  fun prepare(): SessionParamsBuilder {
-    val platformDataResDir = File("${environment.platformDir}/data/res")
-
-    @Suppress("DEPRECATION")
-    val frameworkResources = com.android.ide.common.resources.deprecated.FrameworkResources(
-      FolderWrapper(platformDataResDir)
-    ).apply {
-      loadResources()
-      loadPublicResources(logger)
-    }
-
-    @Suppress("DEPRECATION")
-    val projectResources = object : com.android.ide.common.resources.deprecated.ResourceRepository(
-      FolderWrapper(environment.resDir),
-      false
-    ) {
-      override fun createResourceItem(
-        name: String
-      ): com.android.ide.common.resources.deprecated.ResourceItem {
-        return com.android.ide.common.resources.deprecated.ResourceItem(name)
-      }
-    }
-    projectResources.loadResources()
-
-    sessionParamsBuilder = SessionParamsBuilder(
-      layoutlibCallback = layoutlibCallback,
-      logger = logger,
-      frameworkResources = frameworkResources,
-      projectResources = projectResources,
-      assetRepository = PaparazziAssetRepository(environment.assetsDir)
-    )
-      .plusFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true)
-      .withTheme("AppTheme", true)
-
-    val platformDataRoot = System.getProperty("paparazzi.platform.data.root")
-      ?: throw RuntimeException("Missing system property for 'paparazzi.platform.data.root'")
-    val platformDataDir = File(platformDataRoot, "data")
-    val fontLocation = File(platformDataDir, "fonts")
-    val nativeLibLocation = File(platformDataDir, getNativeLibDir())
-    val icuLocation = File(platformDataDir, "icu" + File.separator + "icudt70l.dat")
-    val buildProp = File(environment.platformDir, "build.prop")
-    val attrs = File(platformDataResDir, "values" + File.separator + "attrs.xml")
-    val systemProperties = DeviceConfig.loadProperties(buildProp) + mapOf(
-      // We want Choreographer.USE_FRAME_TIME to be false so it uses System_Delegate.nanoTime()
-      "debug.choreographer.frametime" to "false"
-    )
-    bridge = Bridge().apply {
-      check(
-        init(
-          systemProperties,
-          fontLocation,
-          nativeLibLocation.path,
-          icuLocation.path,
-          DeviceConfig.getEnumMap(attrs),
-          logger
-        )
-      ) { "Failed to init Bridge." }
-    }
-    Bridge.getLock()
-      .lock()
-    try {
-      Bridge.setLog(logger)
-    } finally {
-      Bridge.getLock()
-        .unlock()
-    }
-
-    return sessionParamsBuilder
-  }
-
-  private fun getNativeLibDir(): String {
-    val osName = System.getProperty("os.name").lowercase(Locale.US)
-    val osLabel = when {
-      osName.startsWith("windows") -> "win"
-      osName.startsWith("mac") -> {
-        val osArch = System.getProperty("os.arch").lowercase(Locale.US)
-        if (osArch.startsWith("x86")) "mac" else "mac-arm"
-      }
-      else -> "linux"
-    }
-    return "$osLabel/lib64"
-  }
-
-  override fun close() {
-    bridge = null
-
-    Gc.gc()
-
-    dumpDelegates()
-  }
-
-  fun dumpDelegates() {
-    if (System.getProperty(Flags.DEBUG_LINKED_OBJECTS) != null) {
-      println("Objects still linked from the DelegateManager:")
-      DelegateManager.dump(System.out)
-    }
-  }
-
-  fun render(
-    bridge: com.android.ide.common.rendering.api.Bridge,
-    params: SessionParams,
-    frameTimeNanos: Long
-  ): RenderResult {
-    val session = bridge.createSession(params)
-
-    try {
-      if (frameTimeNanos != -1L) {
-        session.setElapsedFrameTimeNanos(frameTimeNanos)
-      }
-
-      if (!session.result.isSuccess) {
-        logger.error(session.result.exception, session.result.errorMessage)
-      } else {
-        // Render the session with a timeout of 50s.
-        val renderResult = session.render(50000)
-        if (!renderResult.isSuccess) {
-          logger.error(session.result.exception, session.result.errorMessage)
-        }
-      }
-
-      return session.toResult()
-    } finally {
-      session.dispose()
-    }
-  }
-
-  /** Compares the golden image with the passed image. */
-  fun verify(
-    goldenImageName: String,
-    image: BufferedImage
-  ) {
-    try {
-      val goldenImagePath = environment.appTestDir + "/golden/" + goldenImageName
-      ImageUtils.requireSimilar(goldenImagePath, image, maxPercentDifference)
-    } catch (e: IOException) {
-      logger.error(e, e.message)
-    }
-  }
-
-  /**
-   * Create a new rendering session and test that rendering the given layout doesn't throw any
-   * exceptions and matches the provided image.
-   *
-   * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
-   * how far in the future is.
-   */
-  @JvmOverloads
-  fun renderAndVerify(
-    sessionParams: SessionParams,
-    goldenFileName: String,
-    frameTimeNanos: Long = -1
-  ): RenderResult {
-    val result = render(bridge!!, sessionParams, frameTimeNanos)
-    verify(goldenFileName, result.image)
-    return result
-  }
-
-  fun createParserFromPath(layoutPath: String): LayoutPullParser =
-    LayoutPullParser.createFromPath("${environment.resDir}/layout/$layoutPath")
-
-  /**
-   * Create a new rendering session and test that rendering the given layout on given device
-   * doesn't throw any exceptions and matches the provided image.
-   */
-  @JvmOverloads
-  fun renderAndVerify(
-    layoutFileName: String,
-    goldenFileName: String,
-    deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5
-  ): RenderResult {
-    val sessionParams = sessionParamsBuilder
-      .copy(
-        layoutPullParser = createParserFromPath(layoutFileName),
-        deviceConfig = deviceConfig
-      )
-      .build()
-    return renderAndVerify(sessionParams, goldenFileName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt
deleted file mode 100644
index a7f14c8..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ResourcesInterceptor.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.content.Context
-import android.graphics.Typeface
-
-object ResourcesInterceptor {
-  @JvmStatic
-  fun intercept(
-    context: Context,
-    resId: Int
-  ): Typeface? {
-    return context.resources.getFont(resId)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt
deleted file mode 100644
index 4726fad..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/ServiceManagerInterceptor.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package app.cash.paparazzi.internal
-
-import android.os.IBinder
-
-/**
- * The ImeTracing class attempts to initialize its [mService field in its constructor](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/util/imetracing/ImeTracing.java;l=60).
- *
- * Unfortunately, [layoutlib's version of ServiceManager](https://cs.android.com/android/platform/superproject/+/master:frameworks/layoutlib/bridge/src/android/os/ServiceManager.java;l=37)
- * throws an exception immediately.
- *
- * This interceptor overrides ServiceManager.getServiceOrThrow to simply return null instead.
- */
-object ServiceManagerInterceptor {
-  @Suppress("unused")
-  @JvmStatic
-  fun interceptGetServiceOrThrow(@Suppress("UNUSED_PARAMETER") name: String): IBinder? = null
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt
deleted file mode 100644
index ea263b6..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/SessionParamsBuilder.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.internal.parsers.LayoutPullParser
-import com.android.SdkConstants
-import com.android.ide.common.rendering.api.AssetRepository
-import com.android.ide.common.rendering.api.ResourceNamespace
-import com.android.ide.common.rendering.api.ResourceReference
-import com.android.ide.common.rendering.api.SessionParams
-import com.android.ide.common.rendering.api.SessionParams.Key
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import com.android.ide.common.resources.ResourceResolver
-import com.android.ide.common.resources.ResourceValueMap
-import com.android.layoutlib.bridge.Bridge
-import com.android.resources.LayoutDirection
-import com.android.resources.ResourceType
-
-/** Creates [SessionParams] objects. */
-internal data class SessionParamsBuilder(
-  private val layoutlibCallback: PaparazziCallback,
-  private val logger: PaparazziLogger,
-  @Suppress("DEPRECATION")
-  private val frameworkResources: com.android.ide.common.resources.deprecated.ResourceRepository,
-  private val assetRepository: AssetRepository,
-  @Suppress("DEPRECATION")
-  private val projectResources: com.android.ide.common.resources.deprecated.ResourceRepository,
-  private val deviceConfig: DeviceConfig = DeviceConfig.NEXUS_5,
-  private val renderingMode: RenderingMode = RenderingMode.NORMAL,
-  private val targetSdk: Int = 22,
-  private val flags: Map<Key<*>, Any> = mapOf(),
-  private val themeName: String? = null,
-  private val isProjectTheme: Boolean = false,
-  private val layoutPullParser: LayoutPullParser? = null,
-  private val projectKey: Any? = null,
-  private val minSdk: Int = 0,
-  private val decor: Boolean = true
-) {
-  fun withTheme(
-    themeName: String,
-    isProjectTheme: Boolean
-  ): SessionParamsBuilder {
-    return copy(themeName = themeName, isProjectTheme = isProjectTheme)
-  }
-
-  fun withTheme(themeName: String): SessionParamsBuilder {
-    return when {
-      themeName.startsWith(SdkConstants.PREFIX_ANDROID) -> {
-        withTheme(themeName.substring(SdkConstants.PREFIX_ANDROID.length), false)
-      }
-      else -> withTheme(themeName, true)
-    }
-  }
-
-  fun plusFlag(
-    flag: SessionParams.Key<*>,
-    value: Any
-  ) = copy(flags = flags + (flag to value))
-
-  fun build(): SessionParams {
-    require(themeName != null)
-
-    val folderConfiguration = deviceConfig.folderConfiguration
-
-    @Suppress("DEPRECATION")
-    val resourceResolver = ResourceResolver.create(
-      mapOf<ResourceNamespace, Map<ResourceType, ResourceValueMap>>(
-        ResourceNamespace.ANDROID to frameworkResources.getConfiguredResources(
-          folderConfiguration
-        ),
-        ResourceNamespace.TODO() to projectResources.getConfiguredResources(
-          folderConfiguration
-        )
-      ),
-      ResourceReference(
-        ResourceNamespace.fromBoolean(!isProjectTheme),
-        ResourceType.STYLE,
-        themeName
-      )
-    )
-
-    val result = SessionParams(
-      layoutPullParser, renderingMode, projectKey /* for caching */,
-      deviceConfig.hardwareConfig, resourceResolver, layoutlibCallback, minSdk, targetSdk, logger
-    )
-    result.fontScale = deviceConfig.fontScale
-
-    val localeQualifier = folderConfiguration.localeQualifier
-    val layoutDirectionQualifier = folderConfiguration.layoutDirectionQualifier
-    // https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-main:android/src/com/android/tools/idea/rendering/RenderTask.java;l=645
-    if (
-      LayoutDirection.RTL == layoutDirectionQualifier.value &&
-      !Bridge.isLocaleRtl(localeQualifier.tag)
-    ) {
-      result.locale = "ur"
-    } else {
-      result.locale = localeQualifier.tag
-    }
-    result.setRtlSupport(true)
-
-    for ((key, value) in flags) {
-      @Suppress("UNCHECKED_CAST")
-      result.setFlag(key as Key<Any>, value)
-    }
-    result.setAssetRepository(assetRepository)
-
-    if (!decor) {
-      result.setForceNoDecor()
-    }
-
-    return result
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt
deleted file mode 100644
index 3c62aa8..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrParser.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-/**
- * Copied from https://cs.android.com/android-studio/platform/tools/adt/idea/+/858f81bb7c350bc7a05daad36edefd21f74c8cef:android/src/com/android/tools/idea/rendering/parsers/AaptAttrParser.java
- *
- * Interface for parsers that support declaration of inlined {@code aapt:attr} attributes
- */
-interface AaptAttrParser {
-  /**
-   * Returns a [Map] that contains all the `aapt:attr` elements declared in this or any
-   * children parsers. This list can be used to resolve `@aapt/_aapt` references into this parser.
-   */
-  fun getAaptDeclaredAttrs(): Map<String, TagSnapshot>
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt
deleted file mode 100644
index 4105eee..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AaptAttrSnapshot.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.AAPT_ATTR_PREFIX
-import com.android.SdkConstants.AAPT_PREFIX
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/AaptAttrAttributeSnapshot.java
- *
- * Aapt attributes are attributes that instead of containing a reference, contain the inlined value
- * of the reference. This snapshot will generate a dynamic reference that will be used by the
- * resource resolution to be able to retrieve the inlined value.
- */
-class AaptAttrSnapshot(
-  override val namespace: String,
-  override val prefix: String,
-  override val name: String,
-  val id: String,
-  val bundledTag: TagSnapshot
-) : AttributeSnapshot(namespace, prefix, name, "${AAPT_ATTR_PREFIX}$AAPT_PREFIX$id")
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt
deleted file mode 100644
index 50b658c..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/AttributeSnapshot.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/AttributeSnapshot.java
- *
- * A snapshot of an attribute value pulled from an XML resource.
- * Used in conjunction with [TagSnapshot].
- */
-open class AttributeSnapshot(
-  open val namespace: String,
-  open val prefix: String?,
-  open val name: String,
-  open val value: String
-) {
-  override fun toString() = "$name: $value"
-
-  // since data classes can't be subclassed
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (javaClass != other?.javaClass) return false
-
-    other as AttributeSnapshot
-
-    if (namespace != other.namespace) return false
-    if (prefix != other.prefix) return false
-    if (name != other.name) return false
-    if (value != other.value) return false
-
-    return true
-  }
-
-  override fun hashCode(): Int {
-    var result = namespace.hashCode()
-    result = 31 * result + prefix.hashCode()
-    result = 31 * result + name.hashCode()
-    result = 31 * result + value.hashCode()
-    return result
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt
deleted file mode 100644
index 5b709fb..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/InMemoryParser.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-import org.kxml2.io.KXmlParser
-import org.xmlpull.v1.XmlPullParserException
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/858f81bb7c350bc7a05daad36edefd21f74c8cef:android/src/com/android/tools/idea/rendering/parsers/LayoutPullParser.java;bpv=0;bpt=0
- *
- * A parser implementation that walks an in-memory XML DOM tree.
- */
-abstract class InMemoryParser : KXmlParser() {
-  abstract fun rootTag(): TagSnapshot
-
-  private val nodeStack = mutableListOf<TagSnapshot>()
-  private var parsingState = START_DOCUMENT
-
-  override fun getAttributeCount(): Int {
-    val tag = getCurrentNode() ?: return 0
-    return tag.attributes.size
-  }
-
-  override fun getAttributeName(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.name
-  }
-
-  override fun getAttributeNamespace(i: Int): String {
-    val attribute = getAttribute(i) ?: return ""
-    return attribute.namespace
-  }
-
-  override fun getAttributePrefix(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.prefix
-  }
-
-  override fun getAttributeValue(i: Int): String? {
-    val attribute = getAttribute(i) ?: return null
-    return attribute.value
-  }
-
-  override fun getAttributeValue(
-    namespace: String?,
-    name: String?
-  ): String? {
-    val tag = getCurrentNode() ?: return null
-    return tag.attributes.find { it.name == name }?.value
-  }
-
-  override fun getDepth(): Int = nodeStack.size
-
-  override fun getName(): String? {
-    if (parsingState == START_TAG || parsingState == END_TAG) {
-      // Should only be called when START_TAG
-      val currentNode = getCurrentNode()!!
-      return currentNode.name
-    }
-    return null
-  }
-
-  @Throws(XmlPullParserException::class)
-  override fun next(): Int {
-    when (parsingState) {
-      END_DOCUMENT -> throw XmlPullParserException("Nothing after the end")
-      START_DOCUMENT -> onNextFromStartDocument()
-      START_TAG -> onNextFromStartTag()
-      END_TAG -> onNextFromEndTag()
-    }
-    return parsingState
-  }
-
-  private fun getCurrentNode(): TagSnapshot? = nodeStack.lastOrNull()
-
-  private fun getAttribute(i: Int): AttributeSnapshot? {
-    if (parsingState != START_TAG) {
-      throw IndexOutOfBoundsException()
-    }
-    val tag = getCurrentNode() ?: return null
-    return tag.attributes[i]
-  }
-
-  private fun push(node: TagSnapshot) {
-    nodeStack.add(node)
-  }
-
-  private fun pop(): TagSnapshot = nodeStack.removeLast()
-
-  private fun onNextFromStartDocument() {
-    val rootTag = rootTag()
-    @Suppress("SENSELESS_COMPARISON")
-    parsingState = if (rootTag != null) {
-      push(rootTag)
-      START_TAG
-    } else {
-      END_DOCUMENT
-    }
-  }
-
-  private fun onNextFromStartTag() {
-    // get the current node, and look for text or children (children first)
-    // Should only be called when START_TAG
-    val node = getCurrentNode()!!
-    val children = node.children
-    parsingState = if (children.isNotEmpty()) {
-      // move to the new child, and don't change the state.
-      push(children[0])
-
-      // in case the current state is CURRENT_DOC, we set the proper state.
-      START_TAG
-    } else {
-      if (parsingState == START_DOCUMENT) {
-        // this handles the case where there's no node.
-        END_DOCUMENT
-      } else {
-        END_TAG
-      }
-    }
-  }
-
-  private fun onNextFromEndTag() {
-    // look for a sibling. if no sibling, go back to the parent
-    // Should only be called when END_TAG
-    var node = getCurrentNode()!!
-    val sibling = node.next
-    if (sibling != null) {
-      node = sibling
-      // to go to the sibling, we need to remove the current node,
-      pop()
-      // and add its sibling.
-      push(node)
-      parsingState = START_TAG
-    } else {
-      // move back to the parent
-      pop()
-
-      // we have only one element left (myRoot), then we're done with the document.
-      parsingState = if (nodeStack.isEmpty()) {
-        END_DOCUMENT
-      } else {
-        END_TAG
-      }
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt
deleted file mode 100644
index 8f1a94a..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/LayoutPullParser.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.ATTR_IGNORE
-import com.android.SdkConstants.EXPANDABLE_LIST_VIEW
-import com.android.SdkConstants.GRID_VIEW
-import com.android.SdkConstants.LIST_VIEW
-import com.android.SdkConstants.SPINNER
-import com.android.SdkConstants.TOOLS_URI
-import com.android.ide.common.rendering.api.ILayoutPullParser
-import com.android.ide.common.rendering.api.ResourceNamespace
-import java.io.ByteArrayInputStream
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.IOError
-import java.io.InputStream
-import java.nio.charset.Charset
-import okio.buffer
-import okio.source
-import org.xmlpull.v1.XmlPullParserException
-
-/**
- * A layout parser that holds an in-memory tree of a given resource for subsequent traversal
- * during the inflation process
- */
-internal class LayoutPullParser : InMemoryParser, AaptAttrParser, ILayoutPullParser {
-  private constructor(inputStream: InputStream) : super() {
-    try {
-      val buffer = inputStream.source().buffer()
-
-      setFeature(FEATURE_PROCESS_NAMESPACES, true)
-      setInput(buffer.peek().inputStream(), null)
-
-      // IntelliJ uses XmlFile/PsiFile to parse tag snapshots,
-      // leaving XmlPullParser for Android to parse resources as usual
-      // Here, we use the same XmlPullParser approach for both, which means
-      // we need reinitialize the document stream between the two passes.
-      val resourceParser = ResourceParser(buffer.inputStream())
-      root = resourceParser.createTagSnapshot()
-
-      // Obtain a list of all the aapt declared attributes
-      declaredAaptAttrs = findDeclaredAaptAttrs(root)
-    } catch (e: XmlPullParserException) {
-      throw IOError(e)
-    }
-  }
-
-  private constructor(aaptResource: TagSnapshot) : super() {
-    root = aaptResource
-    declaredAaptAttrs = emptyMap()
-  }
-
-  private val root: TagSnapshot
-  private val declaredAaptAttrs: Map<String, TagSnapshot>
-
-  private var layoutNamespace = ResourceNamespace.RES_AUTO
-
-  override fun rootTag() = root
-
-  @Suppress("SENSELESS_COMPARISON")
-  override fun getViewCookie(): Any? {
-    // TODO: Implement this properly.
-    val name = super.getName() ?: return null
-
-    // Store tools attributes if this looks like a layout we'll need adapter view
-    // bindings for in the LayoutlibCallback.
-    if (LIST_VIEW == name || EXPANDABLE_LIST_VIEW == name || GRID_VIEW == name || SPINNER == name) {
-      var map: MutableMap<String, String>? = null
-      val count = attributeCount
-      for (i in 0 until count) {
-        val namespace = getAttributeNamespace(i)
-        if (namespace != null && namespace == TOOLS_URI) {
-          val attribute = getAttributeName(i)!!
-          if (attribute == ATTR_IGNORE) {
-            continue
-          }
-          if (map == null) {
-            map = HashMap(4)
-          }
-          map[attribute] = getAttributeValue(i)!!
-        }
-      }
-
-      return map
-    }
-
-    return null
-  }
-
-  override fun getLayoutNamespace(): ResourceNamespace = layoutNamespace
-
-  override fun getAaptDeclaredAttrs(): Map<String, TagSnapshot> = declaredAaptAttrs
-
-  fun setLayoutNamespace(layoutNamespace: ResourceNamespace) {
-    this.layoutNamespace = layoutNamespace
-  }
-
-  private fun findDeclaredAaptAttrs(tag: TagSnapshot): Map<String, TagSnapshot> {
-    if (!tag.hasDeclaredAaptAttrs) {
-      // Nor tag or any of the children has any aapt:attr declarations, we can stop here.
-      return emptyMap()
-    }
-
-    return buildMap {
-      tag.attributes
-        .filterIsInstance<AaptAttrSnapshot>()
-        .forEach { attr ->
-          val bundledTag = attr.bundledTag
-          put(attr.id, bundledTag)
-          for (child in bundledTag.children) {
-            putAll(findDeclaredAaptAttrs(child))
-          }
-        }
-      for (child in tag.children) {
-        putAll(findDeclaredAaptAttrs(child))
-      }
-    }
-  }
-
-  companion object {
-    @Throws(FileNotFoundException::class)
-    fun createFromFile(layoutFile: File) = LayoutPullParser(FileInputStream(layoutFile))
-
-    /**
-     * @param layoutPath Must start with '/' and be relative to test resources.
-     */
-    fun createFromPath(layoutPath: String): LayoutPullParser {
-      @Suppress("NAME_SHADOWING") var layoutPath = layoutPath
-      if (layoutPath.startsWith("/")) {
-        layoutPath = layoutPath.substring(1)
-      }
-
-      return LayoutPullParser(
-        LayoutPullParser::class.java.classLoader!!.getResourceAsStream(layoutPath)
-      )
-    }
-
-    fun createFromString(contents: String): LayoutPullParser {
-      return LayoutPullParser(
-        ByteArrayInputStream(contents.toByteArray(Charset.forName("UTF-8")))
-      )
-    }
-
-    fun createFromAaptResource(aaptResource: TagSnapshot): LayoutPullParser {
-      return LayoutPullParser(aaptResource)
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt
deleted file mode 100644
index f90475b..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/ResourceParser.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-import com.android.SdkConstants.AAPT_URI
-import com.android.SdkConstants.TAG_ATTR
-import java.io.InputStream
-import org.kxml2.io.KXmlParser
-
-/**
- * An XML resource parser that creates a tree of [TagSnapshot]s
- */
-class ResourceParser(inputStream: InputStream) : KXmlParser() {
-  init {
-    setFeature(FEATURE_PROCESS_NAMESPACES, true)
-    setInput(inputStream, null)
-
-    require(START_DOCUMENT, null, null)
-    next()
-  }
-
-  fun createTagSnapshot(): TagSnapshot {
-    require(START_TAG, null, null)
-
-    // need to store now, since TagSnapshot is created on end tag after parser mark has moved
-    val tagName = name
-    val tagNamespace = namespace
-    val prefix = prefix
-
-    val attributes = createAttributesForTag()
-
-    var hasDeclaredAaptAttrs = false
-    var last: TagSnapshot? = null
-    val children = mutableListOf<TagSnapshot>()
-    while (eventType != END_DOCUMENT) {
-      when (next()) {
-        START_TAG -> {
-          if (AAPT_URI == namespace) {
-            if (TAG_ATTR == name) {
-              val attrAttribute = createAttrTagSnapshot()
-              if (attrAttribute != null) {
-                attributes += attrAttribute
-                hasDeclaredAaptAttrs = true
-              }
-            }
-            // Since we save the aapt:attr tags as an attribute, we do not save them as a child element. Skip.
-          } else {
-            val child = createTagSnapshot()
-            hasDeclaredAaptAttrs = hasDeclaredAaptAttrs || child.hasDeclaredAaptAttrs
-            children += child
-            if (last != null) {
-              last.next = child
-            }
-            last = child
-          }
-        }
-        END_TAG -> {
-          return TagSnapshot(
-            tagName,
-            tagNamespace,
-            prefix,
-            attributes,
-            children.toList(),
-            hasDeclaredAaptAttrs
-          )
-        }
-      }
-    }
-
-    throw IllegalStateException("We should never reach here")
-  }
-
-  private fun createAttrTagSnapshot(): AaptAttrSnapshot? {
-    require(START_TAG, null, "attr")
-
-    val name = getAttributeValue(null, "name") ?: return null
-    val prefix = findPrefixByQualifiedName(name)
-    val namespace = getNamespace(prefix)
-    val localName = findLocalNameByQualifiedName(name)
-    val id = (++uniqueId).toString()
-
-    var bundleTagSnapshot: TagSnapshot? = null
-    loop@ while (eventType != END_TAG) {
-      when (nextTag()) {
-        START_TAG -> {
-          bundleTagSnapshot = createTagSnapshot()
-        }
-        END_TAG -> {
-          break@loop
-        }
-      }
-    }
-
-    return if (bundleTagSnapshot != null) {
-      // swallow end tag
-      nextTag()
-      require(END_TAG, null, "attr")
-
-      AaptAttrSnapshot(namespace, prefix, localName, id, bundleTagSnapshot)
-    } else {
-      null
-    }
-  }
-
-  private fun findPrefixByQualifiedName(name: String): String {
-    val prefixEnd = name.indexOf(':')
-    return if (prefixEnd > 0) {
-      name.substring(0, prefixEnd)
-    } else ""
-  }
-
-  private fun findLocalNameByQualifiedName(name: String): String {
-    return name.substring(name.indexOf(':') + 1)
-  }
-
-  private fun createAttributesForTag(): MutableList<AttributeSnapshot> {
-    return buildList {
-      for (i in 0 until attributeCount) {
-        add(
-          AttributeSnapshot(
-            getAttributeNamespace(i),
-            getAttributePrefix(i),
-            getAttributeName(i),
-            getAttributeValue(i)
-          )
-        )
-      }
-    }.toMutableList()
-  }
-
-  companion object {
-    private var uniqueId = 0L
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt
deleted file mode 100644
index 19cab20..0000000
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/internal/parsers/TagSnapshot.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-/**
- * Derived from https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-master-dev:android/src/com/android/tools/idea/rendering/parsers/TagSnapshot.java
- *
- * A snapshot of the state of an xml tag.
- *
- * Used by the rendering architecture to be able to hold a consistent view of
- * the layout files across a long rendering operation without holding read locks,
- * as well as to for example let the property sheet evaluate and paint the values
- * of properties as they were at the time of rendering, not as they are at the current
- * instant.
- */
-data class TagSnapshot(
-  val name: String,
-  val namespace: String,
-  val prefix: String?,
-  val attributes: List<AttributeSnapshot>,
-  val children: List<TagSnapshot>,
-  val hasDeclaredAaptAttrs: Boolean = false
-) {
-  var next: TagSnapshot? = null
-
-  @Suppress("unused") // Used for debugging
-  fun printFormatted(): String {
-    indent++
-    val output = """
-      |$name:
-      |${pad(indent + 1)}attributes: ${print(attributes)}
-      |${pad(indent + 1)}children: ${print(children)} 
-    """.trimMargin()
-    indent--
-    return output
-  }
-
-  private fun print(children: List<*>): String {
-    if (children.isEmpty()) return children.toString()
-
-    indent++
-    val output = children.joinToString(
-      prefix = "[\n${pad(indent + 1)}",
-      separator = "\n${pad(indent + 1)}",
-      postfix = "\n${pad(indent)}]"
-    )
-    indent--
-    return output
-  }
-
-  private fun pad(length: Int): String = "  ".repeat(length)
-
-  companion object {
-    var indent = -1
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/main/resources/index.html b/external/paparazzi/paparazzi/src/main/resources/index.html
deleted file mode 100644
index 327f8b0..0000000
--- a/external/paparazzi/paparazzi/src/main/resources/index.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <style type="text/css">
-    body {
-      background: #212121;
-      text-align: center;
-    }
-
-    .screen {
-      margin: 0.2em;
-      display: inline-block;
-      position: relative;
-      overflow: hidden;
-    }
-
-    .screen img, .screen video {
-      width: 300px;
-    }
-
-    div.overlay__hovered {
-        display: block;
-    }
-
-    .overlay {
-      position: absolute;
-      bottom: 0;
-      left: 0;
-      right: 0;
-      background: rgba(25, 0, 237, .68);
-      color: #fff;
-      opacity: 0;
-      display: none;
-      transition: 150ms;
-
-       -webkit-animation-name: slideIn;
-       -webkit-animation-duration: 0.4s;
-      animation-name: slideIn;
-      animation-duration: 0.4s;
-      animation-fill-mode: forwards;
-    }
-
-    @-webkit-keyframes slideIn {
-      from {bottom: -300px; opacity: 0}
-      to {bottom: 0; opacity: 1}
-    }
-
-    @keyframes slideIn {
-      from {bottom: -300px; opacity: 0}
-      to {bottom: 0; opacity: 1}
-    }
-
-    .test__details {
-      text-align: left;
-      padding-left: 1em;
-    }
-
-    .test__details__name,
-    .test__details__class,
-    .test__details__package {
-      line-height: 0.7em;
-    }
-
-    .test__details__timestamp {
-      margin-top: 2em;
-    }
-
-    .test__details__selector {
-      width: 16px;
-      height: 16px;
-      border-radius: 50%;
-      display: inline-block;
-      border: 2px solid #fff;
-      margin: .1em;
-      margin-bottom: 1.3em;
-      margin-top: 1em;
-    }
-
-    .test__details__selector:hover {
-      background-color: white;
-    }
-    </style>
-</head>
-
-<script src="index.js"></script>
-<script src="paparazzi.js"></script>
-
-<body onload="bootstrap()">
-  <span id="rootContainer"/>
-</body>
-</html>
diff --git a/external/paparazzi/paparazzi/src/main/resources/paparazzi.js b/external/paparazzi/paparazzi/src/main/resources/paparazzi.js
deleted file mode 100644
index 5c12ab1..0000000
--- a/external/paparazzi/paparazzi/src/main/resources/paparazzi.js
+++ /dev/null
@@ -1,222 +0,0 @@
-window.runs = {};
-
-class Run {
-  constructor(id, data) {
-    this.id = id;
-    // TODO(oldergod) which entries are required/optional?
-    this.data = data;
-  }
-}
-
-class Shot {
-  constructor(name, test) {
-    this.name = name;
-    this.test = test;
-
-    [, this.package, this.clazz, this.method] = Shot.TestMethodRegex.exec(test);
-
-    this.runs = [];
-  }
-
-  static get TestMethodRegex() {
-    return /^(.*)\.(.*)#(.*)$/;
-  }
-
-  addRun(runId, file, timestamp) {
-    this.runs.push(
-      {
-        'id': runId,
-        'file': file,
-        'timestamp': timestamp
-      }
-    );
-
-    if (file.endsWith('.png')) {
-      this.img.src = file;
-      this.img.style.display = 'inline';
-      this.video.style.display = 'none';
-    } else {
-      this.video.src = file;
-      this.video.style.display = 'inline';
-      this.img.style.display = 'none';
-    }
-    this.timestampP.innerText = timestamp;
-
-    const circle = document.createElement('div');
-    circle.classList.add('test__details__selector', `run-${runId}`);
-    circle.onmouseover = function (e) {
-      if (file.endsWith('.png')) {
-        this.img.src = file;
-      } else {
-        this.video.src = file;
-      }
-
-      for (let shot of Object.values(paparazziRenderer.shots)) {
-        let found = false;
-        for (let run of shot.runs) {
-          if (runId == run.id) {
-            shot.img.src = run.file;
-            shot.timestampP.innerText = run.timestamp;
-
-            found = true;
-            break;
-          }
-        }
-        shot.img.style.opacity = found ? "1" : "0.3";
-      }
-    }.bind(this);
-    this.overlayDiv.appendChild(circle);
-  }
-
-  removeRun(runId) {
-    const index = this.runs.indexOf((run) => run.id == runId);
-    if (index == -1) return;
-
-    this.runs.splice(index, 1);
-  }
-
-  inflate() {
-    const screenDiv = document.createElement('div');
-    screenDiv.classList.add('screen');
-
-    document.rootContainer.appendChild(screenDiv);
-
-    const img = document.createElement('img');
-    const video = document.createElement('video');
-    video.autoplay = 'autoplay';
-    video.muted = 'muted';
-    video.loop = 'loop';
-
-    const overlayDiv = document.createElement('div');
-    overlayDiv.classList.add('overlay');
-
-    screenDiv.appendChild(img);
-    screenDiv.appendChild(video);
-    screenDiv.appendChild(overlayDiv);
-    screenDiv.onmouseover = function (e) {
-      overlayDiv.classList.add('overlay__hovered');
-    }.bind(this);
-    screenDiv.onmouseout = function (e) {
-      overlayDiv.classList.remove('overlay__hovered');
-    }.bind(this);
-
-    const nameP = document.createElement('p');
-    nameP.classList.add('test__details', 'test__details__name');
-
-    const classP = document.createElement('p');
-    classP.classList.add('test__details', 'test__details__class');
-
-    const packageP = document.createElement('p');
-    packageP.classList.add('test__details', 'test__details__package');
-
-    const timestampP = document.createElement('p');
-    timestampP.classList.add('test__details', 'test__details__timestamp');
-
-    overlayDiv.appendChild(nameP);
-    overlayDiv.appendChild(classP);
-    overlayDiv.appendChild(packageP);
-    overlayDiv.appendChild(timestampP);
-
-    nameP.innerText = this.method;
-    if (this.name !== undefined) {
-      nameP.innerText += ` ${this.name}`;
-    }
-    classP.innerText = this.clazz;
-    packageP.innerText = this.package;
-
-    // hold references to the DOM for later updates
-    this.img = img;
-    this.video = video;
-    this.timestampP = timestampP;
-    this.overlayDiv = overlayDiv;
-  }
-}
-
-class PaparazziRenderer {
-  constructor() {
-    // Used for content comparison for we only re-render the updated ones.
-    this.currentRuns = {};
-    // Used to store runs we know won't be updated anymore.
-    this.lockedRunIds = [];
-    this.shots = {}; // Key is `${test}${name}`, Value is a Shot.
-  }
-
-  start() {
-    this.loadRunScript('index.js');
-    for (let runId of window.all_runs) {
-      this.loadRunScript(`runs/${runId}.js`);
-    }
-    setInterval(this.refresh.bind(this), 100);
-  }
-
-  render(run) {
-    if (this.currentRuns[run.id]
-      && JSON.stringify(this.currentRuns[run.id]) == JSON.stringify(run)) {
-      // This run didn't change.
-      return;
-    }
-    this.currentRuns[run.id] = run;
-    console.log('rendering', run);
-
-    for (let datum of run.data) {
-      const key = `${datum.testName}${datum.name}`;
-      let shot = this.shots[key];
-      if (!shot) {
-        console.log('New shot detected', shot);
-        shot = new Shot(datum.name, datum.testName);
-        this.shots[key] = shot;
-        shot.inflate();
-      } else {
-        //shot.removeRun(run.id);
-      }
-
-      console.log('Adding run to shot', shot);
-      shot.addRun(run.id, datum.file, datum.timestamp);
-
-      // TODO setup listeners for filters/hovering, etc
-    }
-  }
-
-  renderAll() {
-    this.loadRunScript('index.js');
-    for (let runId of window.all_runs) {
-      if (this.lockedRunIds.includes(runId)) {
-        continue;
-      }
-      // The js loading is async so the rendering can happen in the next refresh
-      this.loadRunScript(`runs/${runId}.js`);
-
-      this.render(new Run(runId, window.runs[runId]));
-
-      const lastRunId = window.all_runs[window.all_runs.length - 1];
-      if (runId != lastRunId) {
-        // This run isn't the last run so we know it ain't gonna be updated.
-        this.lockedRunIds.push(runId);
-        delete this.currentRuns[runId];
-      }
-    }
-  }
-
-  refresh() {
-    if (window.all_runs.length == 0) return;
-
-    this.renderAll();
-  }
-
-  loadRunScript(js) {
-    const script = document.createElement('script');
-    script.src = js;
-    script.onload = function () {
-      this.remove();
-    }
-    document.head.appendChild(script);
-  }
-}
-
-const paparazziRenderer = new PaparazziRenderer();
-console.log(paparazziRenderer);
-
-function bootstrap() {
-  document.rootContainer = document.getElementById('rootContainer');
-  paparazziRenderer.start();
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt
deleted file mode 100644
index 80979c7..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import java.awt.image.BufferedImage
-import java.io.File
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.attribute.BasicFileAttributes
-import java.nio.file.attribute.FileTime
-import java.time.Instant
-import java.util.Date
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-
-class HtmlReportWriterTest {
-  @get:Rule
-  val reportRoot: TemporaryFolder = TemporaryFolder()
-
-  @get:Rule
-  val snapshotRoot: TemporaryFolder = TemporaryFolder()
-
-  private val anyImage = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
-  private val anyImageHash = "9069ca78e7450a285173431b3e52c5c25299e473"
-
-  @Test
-  fun happyPath() {
-    val htmlReportWriter = HtmlReportWriter("run_one", reportRoot.root)
-    htmlReportWriter.use {
-      val frameHandler = htmlReportWriter.newFrameHandler(
-        Snapshot(
-          name = "loading",
-          testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings"),
-          timestamp = Instant.parse("2019-03-20T10:27:43Z").toDate(),
-          tags = listOf("redesign")
-        ),
-        1,
-        -1
-      )
-      frameHandler.use {
-        frameHandler.handle(anyImage)
-      }
-    }
-
-    assertThat(File("${reportRoot.root}/index.js")).hasContent(
-      """
-        |window.all_runs = [
-        |  "run_one"
-        |];
-        |
-      """.trimMargin()
-    )
-
-    assertThat(File("${reportRoot.root}/runs/run_one.js")).hasContent(
-      """
-        |window.runs["run_one"] = [
-        |  {
-        |    "name": "loading",
-        |    "testName": "app.cash.paparazzi.CelebrityTest#testSettings",
-        |    "timestamp": "2019-03-20T10:27:43.000Z",
-        |    "tags": [
-        |      "redesign"
-        |    ],
-        |    "file": "images/$anyImageHash.png"
-        |  }
-        |];
-        |
-      """.trimMargin()
-    )
-  }
-
-  @Test
-  fun sanitizeForFilename() {
-    assertThat("0 Dollars".sanitizeForFilename()).isEqualTo("0_dollars")
-    assertThat("`!#$%&*+=|\\'\"<>?/".sanitizeForFilename()).isEqualTo("_________________")
-    assertThat("~@^()[]{}:;,.".sanitizeForFilename()).isEqualTo("~@^()[]{}:;,.")
-  }
-
-  @Test
-  fun noSnapshotOnFailure() {
-    val htmlReportWriter = HtmlReportWriter("run_one", reportRoot.root)
-    htmlReportWriter.use {
-      val frameHandler = htmlReportWriter.newFrameHandler(
-        snapshot = Snapshot(
-          name = "loading",
-          testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings"),
-          timestamp = Instant.parse("2019-03-20T10:27:43Z").toDate()
-        ),
-        frameCount = 4,
-        fps = -1
-      )
-      frameHandler.use {
-        // intentionally empty, to simulate no content written on exception
-      }
-    }
-
-    assertThat(File(reportRoot.root, "images")).isEmptyDirectory
-    assertThat(File(reportRoot.root, "videos")).isEmptyDirectory
-  }
-
-  @Test
-  fun alwaysOverwriteOnRecord() {
-    // set record mode
-    System.setProperty("paparazzi.test.record", "true")
-
-    val htmlReportWriter = HtmlReportWriter("record_run", reportRoot.root, snapshotRoot.root)
-    htmlReportWriter.use {
-      val now = Instant.parse("2021-02-23T10:27:43Z")
-      val snapshot = Snapshot(
-        name = "test",
-        testName = TestName("app.cash.paparazzi", "HomeView", "testSettings"),
-        timestamp = now.toDate()
-      )
-      val file =
-        File("${snapshotRoot.root}/images/app.cash.paparazzi_HomeView_testSettings_test.png")
-      val golden = file.toPath()
-
-      // precondition
-      assertThat(golden).doesNotExist()
-
-      // take 1
-      val frameHandler1 = htmlReportWriter.newFrameHandler(
-        snapshot = snapshot,
-        frameCount = 1,
-        fps = -1
-      )
-      frameHandler1.use { frameHandler1.handle(anyImage) }
-      assertThat(golden).exists()
-      val timeFirstWrite = golden.lastModifiedTime()
-
-      // I know....but guarantees writes won't happen in same tick
-      Thread.sleep(100)
-
-      // take 2
-      val frameHandler2 = htmlReportWriter.newFrameHandler(
-        snapshot = snapshot.copy(timestamp = now.plusSeconds(1).toDate()),
-        frameCount = 1,
-        fps = -1
-      )
-      frameHandler2.use { frameHandler2.handle(anyImage) }
-      assertThat(golden).exists()
-      val timeOverwrite = golden.lastModifiedTime()
-
-      // should always overwrite
-      assertThat(timeOverwrite).isGreaterThan(timeFirstWrite)
-    }
-  }
-
-  private fun Instant.toDate() = Date(toEpochMilli())
-
-  private fun Path.lastModifiedTime(): FileTime {
-    return Files.readAttributes(this, BasicFileAttributes::class.java).lastModifiedTime()
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt
deleted file mode 100644
index 356a6c8..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/PaparazziTest.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-import android.animation.AnimationHandler
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.graphics.Canvas
-import android.graphics.Color
-import android.view.Choreographer
-import android.view.Choreographer.CALLBACK_ANIMATION
-import android.view.View
-import android.view.animation.LinearInterpolator
-import android.widget.Button
-import com.android.internal.lang.System_Delegate
-import java.util.concurrent.TimeUnit
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-class PaparazziTest {
-  @get:Rule
-  val paparazzi = Paparazzi()
-
-  @Ignore("b/245941625")
-  @Test
-  fun drawCalls() {
-    val log = mutableListOf<String>()
-
-    val view = object : View(paparazzi.context) {
-      override fun onDraw(canvas: Canvas) {
-        log += "onDraw time=$time"
-      }
-    }
-
-    paparazzi.snapshot(view)
-
-    assertThat(log).containsExactly("onDraw time=0")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun resetsAnimationHandler() {
-    assertThat(AnimationHandler.sAnimatorHandler.get()).isNull()
-
-    // Why Button?  Because it sets a StateListAnimator on window attach
-    // See https://github.com/cashapp/paparazzi/pull/319
-    paparazzi.snapshot(Button(paparazzi.context))
-
-    assertThat(AnimationHandler.sAnimatorHandler.get()).isNull()
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun animationEvents() {
-    val log = mutableListOf<String>()
-
-    val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
-    animator.addListener(object : AnimatorListenerAdapter() {
-      override fun onAnimationStart(animation: Animator) {
-        log += "onAnimationStart time=$time animationElapsed=${animator.animatedValue}"
-      }
-
-      override fun onAnimationEnd(animation: Animator) {
-        log += "onAnimationEnd time=$time animationElapsed=${animator.animatedValue}"
-      }
-    })
-
-    val view = object : View(paparazzi.context) {
-      override fun onDraw(canvas: Canvas) {
-        log += "onDraw time=$time animationElapsed=${animator.animatedValue}"
-      }
-    }
-
-    animator.addUpdateListener {
-      log += "onAnimationUpdate time=$time animationElapsed=${animator.animatedValue}"
-
-      val colorComponent = it.animatedFraction
-      view.setBackgroundColor(Color.argb(1f, colorComponent, colorComponent, colorComponent))
-    }
-
-    animator.startDelay = 2_000L
-    animator.duration = 1_000L
-    animator.interpolator = LinearInterpolator()
-    animator.start()
-
-    paparazzi.gif(view, start = 1_000L, end = 4_000L, fps = 4)
-
-    assertThat(log).containsExactly(
-      "onDraw time=1000 animationElapsed=0.0",
-      "onAnimationStart time=2000 animationElapsed=0.0",
-      "onAnimationUpdate time=2000 animationElapsed=0.0",
-      "onDraw time=2000 animationElapsed=0.0",
-      "onAnimationUpdate time=2250 animationElapsed=0.25",
-      "onDraw time=2250 animationElapsed=0.25",
-      "onAnimationUpdate time=2500 animationElapsed=0.5",
-      "onDraw time=2500 animationElapsed=0.5",
-      "onAnimationUpdate time=2750 animationElapsed=0.75",
-      "onDraw time=2750 animationElapsed=0.75",
-      "onAnimationUpdate time=3000 animationElapsed=1.0",
-      "onAnimationEnd time=3000 animationElapsed=1.0",
-      "onDraw time=3000 animationElapsed=1.0"
-    )
-  }
-
-  @Test
-  @Ignore
-  fun frameCallbacksExecutedAfterLayout() {
-    val log = mutableListOf<String>()
-
-    val view = object : View(paparazzi.context) {
-      override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        Choreographer.getInstance()
-          .postCallback(
-            CALLBACK_ANIMATION,
-            { log += "view width=$width height=$height" },
-            false
-          )
-      }
-    }
-
-    paparazzi.snapshot(view)
-
-    assertThat(log).containsExactly("view width=1080 height=1776")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun throwsRenderingExceptions() {
-    val view = object : View(paparazzi.context) {
-      override fun onAttachedToWindow() {
-        throw Throwable("Oops")
-      }
-    }
-
-    val thrown = try {
-      paparazzi.snapshot(view)
-      false
-    } catch (exception: Throwable) {
-      true
-    }
-
-    assertThat(thrown).isTrue
-  }
-
-  private val time: Long
-    get() {
-      return TimeUnit.NANOSECONDS.toMillis(System_Delegate.nanoTime() - Paparazzi.TIME_OFFSET_NANOS)
-    }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt
deleted file mode 100644
index 5827e33..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/R.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi
-
-/** Simulate an empty R.java for this package. */
-class R
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt
deleted file mode 100644
index d774099..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/RenderingModeTest.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-package app.cash.paparazzi
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.internal.ImageUtils
-import com.android.ide.common.rendering.api.SessionParams
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.Description
-
-class RenderingModeTest {
-
-  @Ignore("b/245941625")
-  @Test
-  fun `shrinks to wrap view`() {
-    Paparazzi(
-      snapshotHandler = TestSnapshotVerifier(),
-      deviceConfig = DeviceConfig.NEXUS_5.copy(
-        softButtons = false
-      ),
-      renderingMode = SessionParams.RenderingMode.SHRINK
-    ).runTest("shrinks to wrap view") {
-      val view = buildView(
-        context,
-        ViewGroup.LayoutParams(
-          ViewGroup.LayoutParams.WRAP_CONTENT,
-          ViewGroup.LayoutParams.WRAP_CONTENT
-        )
-      )
-      snapshot(view, "rendering-mode-shrink")
-    }
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `renders full device with RenderingMode NORMAL`() {
-    Paparazzi(
-      snapshotHandler = TestSnapshotVerifier(true),
-      deviceConfig = DeviceConfig.NEXUS_5.copy(
-        softButtons = false
-      ),
-      renderingMode = SessionParams.RenderingMode.NORMAL
-    ).runTest("renders full device with RenderingMode NORMAL") {
-      val view = buildView(
-        context,
-        ViewGroup.LayoutParams(
-          ViewGroup.LayoutParams.WRAP_CONTENT,
-          ViewGroup.LayoutParams.WRAP_CONTENT
-        )
-      )
-      snapshot(view, "rendering-mode-normal")
-    }
-  }
-
-  private fun Paparazzi.runTest(name: String, body: Paparazzi.() -> Unit) {
-    try {
-      prepare(Description.createTestDescription(this@RenderingModeTest::class.java, name))
-      body()
-    } finally {
-      close()
-    }
-  }
-
-  private fun buildView(
-    context: Context,
-    rootLayoutParams: ViewGroup.LayoutParams? = ViewGroup.LayoutParams(
-      ViewGroup.LayoutParams.MATCH_PARENT,
-      ViewGroup.LayoutParams.MATCH_PARENT
-    )
-  ) =
-    LinearLayout(context).apply {
-      orientation = LinearLayout.VERTICAL
-      rootLayoutParams?.let { layoutParams = it }
-      addView(
-        TextView(context).apply {
-          id = 1
-          text = "Text View Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 2
-          layoutParams = LinearLayout.LayoutParams(100, 100)
-          contentDescription = "Content Description Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 3
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          contentDescription = "Margin Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 4
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          foreground = GradientDrawable(
-            GradientDrawable.Orientation.TL_BR,
-            intArrayOf(Color.YELLOW, Color.BLUE)
-          ).apply {
-            shape = GradientDrawable.OVAL
-          }
-          contentDescription = "Foreground Drawable"
-        }
-      )
-
-      addView(
-        Button(context).apply {
-          id = 5
-          layoutParams = LinearLayout.LayoutParams(
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          ).apply {
-            gravity = Gravity.CENTER
-          }
-          text = "Button Sample"
-        }
-      )
-    }
-}
-
-private class TestSnapshotVerifier(val writeImages: Boolean = false) : SnapshotHandler {
-  override fun newFrameHandler(
-    snapshot: Snapshot,
-    frameCount: Int,
-    fps: Int
-  ): SnapshotHandler.FrameHandler {
-    return object : SnapshotHandler.FrameHandler {
-      override fun handle(image: BufferedImage) {
-        val expected = File("src/test/resources/${snapshot.name}.png")
-        if (writeImages) {
-          ImageIO.write(image, "png", expected)
-        } else {
-          ImageUtils.assertImageSimilar(
-            relativePath = expected.path,
-            image = image,
-            goldenImage = ImageIO.read(expected),
-            maxPercentDifferent = 0.1
-          )
-        }
-      }
-
-      override fun close() = Unit
-    }
-  }
-
-  override fun close() = Unit
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt
deleted file mode 100644
index b9afe6e..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/accessibility/AccessibilityRenderExtensionTest.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package app.cash.paparazzi.accessibility
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.GradientDrawable.OVAL
-import android.graphics.drawable.GradientDrawable.Orientation.TL_BR
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Paparazzi
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.SnapshotHandler
-import app.cash.paparazzi.internal.ImageUtils
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-class AccessibilityRenderExtensionTest {
-  private val snapshotHandler = TestSnapshotVerifier()
-
-  @get:Rule
-  val paparazzi = Paparazzi(
-    deviceConfig = DeviceConfig.NEXUS_5.copy(
-      // Needed to render accessibility content next to main content.
-      screenWidth = DeviceConfig.NEXUS_5.screenWidth * 2,
-      softButtons = false
-    ),
-    snapshotHandler = snapshotHandler,
-    renderExtensions = setOf(AccessibilityRenderExtension())
-  )
-
-  @Ignore("b/245941625")
-  @Test
-  fun `verify baseline`() {
-    val view = buildView(paparazzi.context)
-    paparazzi.snapshot(view, name = "accessibility")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `test without layout params set`() {
-    val view = buildView(paparazzi.context, null)
-    paparazzi.snapshot(view, name = "without-layout-params")
-  }
-
-  @Ignore("b/245941625")
-  @Test
-  fun `verify changing view hierarchy order doesn't change accessibility colors`() {
-    val view = buildView(paparazzi.context).apply {
-      addView(
-        View(context).apply { contentDescription = "Empty View" },
-        0,
-        LinearLayout.LayoutParams(0, 0)
-      )
-    }
-    paparazzi.snapshot(view, name = "accessibility-new-view")
-  }
-
-  private fun buildView(
-    context: Context,
-    rootLayoutParams: ViewGroup.LayoutParams? = ViewGroup.LayoutParams(
-      ViewGroup.LayoutParams.MATCH_PARENT,
-      ViewGroup.LayoutParams.MATCH_PARENT
-    )
-  ) =
-    LinearLayout(context).apply {
-      orientation = LinearLayout.VERTICAL
-      rootLayoutParams?.let { layoutParams = it }
-      addView(
-        TextView(context).apply {
-          id = 1
-          text = "Text View Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 2
-          layoutParams = LinearLayout.LayoutParams(100, 100)
-          contentDescription = "Content Description Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 3
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          contentDescription = "Margin Sample"
-        }
-      )
-
-      addView(
-        View(context).apply {
-          id = 4
-          layoutParams = LinearLayout.LayoutParams(100, 100).apply {
-            setMarginsRelative(20, 20, 20, 20)
-          }
-          foreground = GradientDrawable(TL_BR, intArrayOf(Color.YELLOW, Color.BLUE)).apply {
-            shape = OVAL
-          }
-          contentDescription = "Foreground Drawable"
-        }
-      )
-
-      addView(
-        Button(context).apply {
-          id = 5
-          layoutParams = LinearLayout.LayoutParams(
-            ViewGroup.LayoutParams.WRAP_CONTENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-          ).apply {
-            gravity = Gravity.CENTER
-          }
-          text = "Button Sample"
-        }
-      )
-    }
-
-  private class TestSnapshotVerifier : SnapshotHandler {
-    override fun newFrameHandler(
-      snapshot: Snapshot,
-      frameCount: Int,
-      fps: Int
-    ): SnapshotHandler.FrameHandler {
-      return object : SnapshotHandler.FrameHandler {
-        override fun handle(image: BufferedImage) {
-          val expected = File("src/test/resources/${snapshot.name}.png")
-          ImageUtils.assertImageSimilar(
-            relativePath = expected.path,
-            image = image,
-            goldenImage = ImageIO.read(expected),
-            maxPercentDifferent = 0.1
-          )
-        }
-
-        override fun close() = Unit
-      }
-    }
-
-    override fun close() = Unit
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt
deleted file mode 100644
index 9752037..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziJsonTest.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.TestName
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-
-class PaparazziJsonTest {
-  @Test
-  fun testName() {
-    val adapter = PaparazziJson.moshi.adapter(TestName::class.java)
-    val testName = TestName("app.cash.paparazzi", "CelebrityTest", "testSettings")
-    val json = "\"app.cash.paparazzi.CelebrityTest#testSettings\""
-    assertThat(adapter.toJson(testName)).isEqualTo(json)
-    assertThat(adapter.fromJson(json)).isEqualTo(testName)
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt
deleted file mode 100644
index 89d7823..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/PaparazziLoggerTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package app.cash.paparazzi.internal
-
-import app.cash.paparazzi.internal.PaparazziLogger.MultipleFailuresException
-import java.io.FileNotFoundException
-import org.assertj.core.api.Assertions.assertThat
-import org.assertj.core.api.Assertions.fail
-import org.junit.Test
-
-class PaparazziLoggerTest {
-  @Test
-  fun testNoErrors() {
-    val logger = PaparazziLogger()
-
-    try {
-      logger.assertNoErrors()
-    } catch (ignored: Exception) {
-      fail("Did not expect exception to be thrown: $ignored")
-    }
-  }
-
-  @Test
-  fun testSingleError() {
-    val logger = PaparazziLogger()
-    logger.error(FileNotFoundException("error1"), null)
-
-    try {
-      logger.assertNoErrors()
-      fail("Expected exception to be thrown")
-    } catch (ignored: Exception) {
-      assertThat(ignored).isInstanceOf(FileNotFoundException::class.java)
-    }
-  }
-
-  @Test
-  fun testMultipleErrors() {
-    val logger = PaparazziLogger()
-    logger.error(FileNotFoundException("error1"), null)
-    logger.error("tag", null, IllegalStateException("error2"), null, null)
-
-    try {
-      logger.assertNoErrors()
-      fail("Expected exceptions to be thrown")
-    } catch (ignored: Exception) {
-      assertThat(ignored).isInstanceOf(MultipleFailuresException::class.java)
-      assertThat(ignored.message).contains("There were 2 errors:")
-      assertThat(ignored.message).contains("java.io.FileNotFoundException: error1")
-      assertThat(ignored.message).contains("java.lang.IllegalStateException: error2")
-    }
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt
deleted file mode 100644
index 9a032e3..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/InMemoryParserTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package app.cash.paparazzi.internal.parsers
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-import org.xmlpull.v1.XmlPullParserException
-
-class InMemoryParserTest {
-  @Test
-  fun parse() {
-    val root = parseResourceTree("plus_sign.xml")
-    val parser = RealInMemoryParser(root)
-
-    assertThat(parser.name).isNull() // START_DOCUMENT
-    assertThat(parser.depth).isEqualTo(0)
-
-    parser.next() // START_TAG = "vector"
-
-    assertThat(parser.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(1)
-    assertThat(parser.attributeCount).isEqualTo(4)
-
-    assertThat(parser.getAttributeName(0)).isEqualTo("height")
-    assertThat(parser.getAttributeName(1)).isEqualTo("viewportHeight")
-    assertThat(parser.getAttributeName(2)).isEqualTo("viewportWidth")
-    assertThat(parser.getAttributeName(3)).isEqualTo("width")
-
-    assertThat(parser.getAttributeNamespace(0)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(1)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(2)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(3)).isEqualTo(ANDROID_NAMESPACE)
-
-    assertThat(parser.getAttributePrefix(0)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(1)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(2)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(3)).isEqualTo(ANDROID_PREFIX)
-
-    assertThat(parser.getAttributeValue(0)).isEqualTo("24dp")
-    assertThat(parser.getAttributeValue(1)).isEqualTo("40")
-    assertThat(parser.getAttributeValue(2)).isEqualTo("40")
-    assertThat(parser.getAttributeValue(3)).isEqualTo("24dp")
-
-    parser.next() // START_TAG = "path"
-
-    assertThat(parser.name).isEqualTo(PATH_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(2)
-    assertThat(parser.attributeCount).isEqualTo(2)
-
-    assertThat(parser.getAttributeName(0)).isEqualTo(FILL_COLOR_ATTR_NAME)
-    assertThat(parser.getAttributeName(1)).isEqualTo(PATH_DATA_ATTR_NAME)
-
-    assertThat(parser.getAttributeNamespace(0)).isEqualTo(ANDROID_NAMESPACE)
-    assertThat(parser.getAttributeNamespace(1)).isEqualTo(ANDROID_NAMESPACE)
-
-    assertThat(parser.getAttributePrefix(0)).isEqualTo(ANDROID_PREFIX)
-    assertThat(parser.getAttributePrefix(1)).isEqualTo(ANDROID_PREFIX)
-
-    assertThat(parser.getAttributeValue(0)).isEqualTo("#999999")
-    assertThat(parser.getAttributeValue(1)).isNotNull // pathData
-
-    parser.next() // END_TAG = "path"
-
-    assertThat(parser.name).isEqualTo(PATH_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(2)
-
-    parser.next() // END_TAG = "vector"
-
-    assertThat(parser.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(parser.depth).isEqualTo(1)
-
-    parser.next() // END_DOCUMENT
-    assertThat(parser.name).isNull() // START_DOCUMENT
-    assertThat(parser.depth).isEqualTo(0)
-
-    try {
-      parser.next()
-    } catch (expected: XmlPullParserException) {
-    }
-  }
-
-  private fun parseResourceTree(resourceId: String): TagSnapshot {
-    val resourceInputStream = javaClass.classLoader.getResourceAsStream(resourceId)!!
-    return ResourceParser(resourceInputStream).createTagSnapshot()
-  }
-
-  class RealInMemoryParser(private val root: TagSnapshot) : InMemoryParser() {
-    override fun rootTag(): TagSnapshot = root
-  }
-
-  companion object {
-    const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
-    const val ANDROID_PREFIX = "android"
-
-    const val VECTOR_TAG_NAME = "vector"
-    const val PATH_TAG_NAME = "path"
-    const val PATH_DATA_ATTR_NAME = "pathData"
-    const val FILL_COLOR_ATTR_NAME = "fillColor"
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt b/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt
deleted file mode 100644
index 67402f9..0000000
--- a/external/paparazzi/paparazzi/src/test/java/app/cash/paparazzi/internal/parsers/ResourceParserTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2021 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package app.cash.paparazzi.internal.parsers
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-
-class ResourceParserTest {
-  @Test
-  fun parseResource() {
-    val root = parseResourceTree("plus_sign.xml")
-    assertThat(root.namespace).isEmpty()
-    assertThat(root.prefix).isNull()
-    assertThat(root.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(root.hasDeclaredAaptAttrs).isEqualTo(false)
-    assertThat(root.next).isNull()
-    assertThat(root.attributes).containsExactly(
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "height", "24dp"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "viewportHeight", "40"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "viewportWidth", "40"),
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, "width", "24dp")
-    )
-
-    val pathElement = root.children.single()
-    with(pathElement) {
-      assertThat(namespace).isEmpty()
-      assertThat(prefix).isNull()
-      assertThat(name).isEqualTo(PATH_TAG_NAME)
-      assertThat(hasDeclaredAaptAttrs).isEqualTo(false)
-      assertThat(next).isNull()
-    }
-
-    val pathAttributes = pathElement.attributes
-    assertThat(pathAttributes).hasSize(2)
-    assertThat(pathAttributes[0]).isEqualTo(
-      AttributeSnapshot(ANDROID_NAMESPACE, ANDROID_PREFIX, FILL_COLOR_ATTR_NAME, "#999999")
-    )
-
-    with(pathAttributes[1]) {
-      assertThat(namespace).isEqualTo(ANDROID_NAMESPACE)
-      assertThat(prefix).isEqualTo(ANDROID_PREFIX)
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(value).isNotEmpty // don't care about pathData precision
-    }
-  }
-
-  @Test
-  fun parseAaptAttrTags() {
-    // Since #parseResource covers the basics, this test can be more targeted
-
-    val root = parseResourceTree("card_chip.xml")
-    assertThat(root.name).isEqualTo(VECTOR_TAG_NAME)
-    assertThat(root.hasDeclaredAaptAttrs).isEqualTo(true)
-    assertThat(root.next).isNull()
-
-    assertThat(root.children).hasSize(2)
-
-    val outerPathElement = root.children[0]
-    assertThat(outerPathElement.hasDeclaredAaptAttrs).isEqualTo(false)
-
-    val groupElement = root.children[1]
-    assertThat(groupElement.hasDeclaredAaptAttrs).isEqualTo(true)
-
-    assertThat(outerPathElement.next).isEqualTo(groupElement)
-
-    val clipPathElement = groupElement.children[0]
-    assertThat(clipPathElement.hasDeclaredAaptAttrs).isEqualTo(false)
-
-    val innerPathElement1 = groupElement.children[1]
-    assertThat(innerPathElement1.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement1.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement1.attributes[1] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("1")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt1")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-
-    val innerPathElement2 = groupElement.children[2]
-    assertThat(innerPathElement2.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement2.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement2.attributes[1] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("2")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt2")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-
-    val innerPathElement3 = groupElement.children[3]
-    assertThat(innerPathElement3.hasDeclaredAaptAttrs).isEqualTo(true)
-    with(innerPathElement3.attributes[0]) {
-      assertThat(name).isEqualTo(PATH_DATA_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement3.attributes[1]) {
-      assertThat(name).isEqualTo(FILL_TYPE_ATTR_NAME)
-      assertThat(this).isNotInstanceOf(AaptAttrSnapshot::class.java)
-    }
-    with(innerPathElement3.attributes[2] as AaptAttrSnapshot) {
-      assertThat(name).isEqualTo(FILL_COLOR_ATTR_NAME)
-      assertThat(id).isEqualTo("3")
-      assertThat(value).isEqualTo("@aapt:_aapt/aapt3")
-      assertThat(bundledTag.name).isEqualTo(GRADIENT_TAG_NAME) // 🎉
-    }
-  }
-
-  private fun parseResourceTree(resourceId: String): TagSnapshot {
-    val resourceInputStream = javaClass.classLoader.getResourceAsStream(resourceId)!!
-    return ResourceParser(resourceInputStream).createTagSnapshot()
-  }
-
-  companion object {
-    const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
-    const val ANDROID_PREFIX = "android"
-
-    const val VECTOR_TAG_NAME = "vector"
-    const val PATH_TAG_NAME = "path"
-    const val PATH_DATA_ATTR_NAME = "pathData"
-    const val FILL_COLOR_ATTR_NAME = "fillColor"
-    const val FILL_TYPE_ATTR_NAME = "fillType"
-    const val GRADIENT_TAG_NAME = "gradient"
-  }
-}
diff --git a/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png b/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png
deleted file mode 100644
index 57f25b5a..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/accessibility-new-view.png
+++ /dev/null
Binary files differ
diff --git a/external/paparazzi/paparazzi/src/test/resources/accessibility.png b/external/paparazzi/paparazzi/src/test/resources/accessibility.png
deleted file mode 100644
index d7f3a16..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/accessibility.png
+++ /dev/null
Binary files differ
diff --git a/external/paparazzi/paparazzi/src/test/resources/card_chip.xml b/external/paparazzi/paparazzi/src/test/resources/card_chip.xml
deleted file mode 100644
index a9cb398..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/card_chip.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--Copyright Square, Inc.-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="42dp" android:height="34dp" android:viewportWidth="42" android:viewportHeight="34">
-    <path android:pathData="M36.795 0H5.205C2.333 0 0 2.384 0 5.318v22.374c0 2.934 2.333 5.318 5.205 5.318h31.59c2.872 0 5.205-2.384 5.205-5.318V5.318C41.955 2.384 39.622 0 36.795 0z" android:fillColor="#666666"/>
-    <group>
-        <clip-path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z"/>
-        <path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="3.4928" android:startX="1.38832" android:endY="46.3417" android:endX="68.7929" android:type="linear">
-                    <item android:offset="0" android:color="#FFFFFFFF"/>
-                    <item android:offset="0.4081" android:color="#FFC7B299"/>
-                    <item android:offset="1" android:color="#FF998675"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-        <path android:pathData="M0.314 21.549h11.981c0.404 0 0.808 0.183 1.122 0.504 1.48 1.559 3.5 2.338 6.058 2.338h0.224c2.378 0 3.814-0.825 3.814-2.109 0-1.192-1.211-1.742-4.711-3.072-3.097-1.146-7.18-3.163-7.45-7.748H0.27v0.459h0.404v9.17H0.27v0.458h0.045zm20.866 7.977c5.519-0.504 8.93-3.53 8.93-7.977 0-4.31-3.994-6.281-7.898-7.611-2.02-0.78-3.5-1.421-4.128-2.476h-6.058c0.27 4.172 4.083 6.052 7 7.107 3.32 1.237 5.115 1.97 5.115 3.713 0 1.742-1.705 2.797-4.487 2.797H19.43c-2.737 0-4.936-0.871-6.507-2.568-0.18-0.183-0.403-0.275-0.628-0.275H0.27v0.459h0.403v4.997c0 1.238 0.494 2.384 1.257 3.21L1.705 31.13l0.314 0.32 0.27-0.274c0.807 0.687 1.795 1.1 2.916 1.1h9.02l0.493-2.568c0.045-0.137-0.045-0.229-0.18-0.275-1.704-0.504-3.275-1.33-4.666-2.475-0.134-0.138-0.18-0.321-0.045-0.459 0.135-0.137 0.314-0.183 0.449-0.046 1.346 1.055 2.827 1.88 4.442 2.338 0.449 0.138 0.718 0.596 0.629 1.055l-0.45 2.43h5.026l0.45-2.063c0.089-0.367 0.403-0.642 0.807-0.688zM0.314 10.774h10.994c0.135-5.364 4.801-7.61 9.154-7.931 0.09-0.046 0.18-0.092 0.18-0.23L21 0.644H5.205c-1.121 0-2.109 0.412-2.916 1.1l-0.27-0.23L1.705 1.88 1.93 2.11C1.167 2.934 0.673 4.08 0.673 5.318v4.997H0.27v0.458h0.045zm41.327 11.417H30.738c-0.315 4.447-3.904 7.518-9.513 7.977-0.09 0-0.18 0.092-0.225 0.183l-0.404 1.926H36.75c1.122 0 2.11-0.413 2.917-1.1l0.27 0.275 0.313-0.321-0.224-0.23c0.808-0.825 1.301-1.97 1.301-3.209v-4.997h0.36L41.64 22.19zm-0.314-11.875V5.319c0-1.238-0.493-2.385-1.301-3.21L40.25 1.88l-0.314-0.367-0.224 0.23c-0.808-0.688-1.795-1.1-2.917-1.1h-9.333l-0.539 2.75c-0.044 0.092 0.045 0.23 0.135 0.275 1.212 0.458 2.378 1.1 3.455 1.834 0.135 0.092 0.18 0.32 0.09 0.458-0.045 0.092-0.18 0.138-0.27 0.138-0.044 0-0.134 0-0.179-0.046-1.032-0.688-2.109-1.284-3.276-1.696-0.403-0.138-0.673-0.596-0.583-1.009l0.539-2.613h-5.16l-0.36 2.017c-0.09 0.413-0.403 0.688-0.807 0.734-4.039 0.32-8.391 2.384-8.481 7.29h5.878c-0.045-0.138-0.045-0.276-0.045-0.413 0-1.467 1.436-2.384 3.904-2.384 1.795 0 4.577 0.642 6.641 2.476 0.224 0.183 0.494 0.32 0.763 0.32h12.564v-0.458h-0.404zm0 1.146H29.122c-0.404 0-0.852-0.183-1.211-0.458-1.93-1.697-4.533-2.339-6.193-2.339-0.538 0-3.23 0.092-3.23 1.697 0 1.33 1.57 2.017 3.948 2.934 4.128 1.421 8.302 3.484 8.302 8.253h10.948V21.09h-0.359v-9.17h0.36v-0.458h-0.36z">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="31.3798" android:startX="42.1542" android:endY="-9.12049" android:endX="13.8976" android:type="linear">
-                    <item android:offset="0" android:color="#FFF1EAE2"/>
-                    <item android:offset="0.149949" android:color="#FFF3ECE3"/>
-                    <item android:offset="0.319891" android:color="#FFF4EDE4"/>
-                    <item android:offset="0.509302" android:color="#FFEFE6DB"/>
-                    <item android:offset="0.707905" android:color="#FFECE4DB"/>
-                    <item android:offset="1" android:color="#FFF1F1F1"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-        <path android:pathData="M1.25 0.5C1.08 0.775 1.166 1 1.441 1S2.08 0.775 2.25 0.5C2.42 0.225 2.334 0 2.059 0S1.42 0.225 1.25 0.5zM24 1.03c0 0.565 0.225 0.89 0.5 0.72 0.275-0.17 0.5-0.633 0.5-1.03C25 0.324 24.775 0 24.5 0S24 0.464 24 1.03zm4-0.78c0 0.138 0.113 0.25 0.25 0.25s0.25-0.112 0.25-0.25S28.387 0 28.25 0 28 0.113 28 0.25zm-8.181 2C19.661 2.663 19.749 3 20.016 3 20.282 3 20.5 2.663 20.5 2.25S20.412 1.5 20.303 1.5c-0.108 0-0.326 0.337-0.484 0.75zM24 2.75C24 2.888 24.113 3 24.25 3s0.25-0.112 0.25-0.25-0.113-0.25-0.25-0.25S24 2.612 24 2.75zm-4.76 1.78c-0.45 1.42 0.05 1.796 0.721 0.543 0.326-0.609 0.357-1.127 0.077-1.3-0.256-0.158-0.615 0.182-0.797 0.757zM22.83 4c-0.977 3.02-0.495 6.254 0.669 4.5 0.274-0.412 0.448-1.088 0.387-1.5-0.06-0.412 0.046-0.998 0.236-1.301 0.191-0.303 0.033-0.978-0.351-1.5C23.19 3.412 23.03 3.378 22.829 4zm2.895 1.875C24.304 9.252 23.56 14.5 24.5 14.5c0.275 0 0.5-0.575 0.5-1.278C25 11.64 26.04 7.34 26.504 7c0.188-0.138 0.493-0.869 0.678-1.625 0.501-2.046-0.536-1.69-1.457 0.5zM32 4.75C32 4.888 32.112 5 32.25 5s0.25-0.112 0.25-0.25-0.112-0.25-0.25-0.25S32 4.612 32 4.75zM0.508 5.875c0.004 0.206 0.217 0.706 0.473 1.11 0.576 0.911 1.251 0.341 0.846-0.714-0.298-0.779-1.333-1.09-1.32-0.396zM13 6.029c0 0.292 0.225 0.391 0.5 0.221S14 5.842 14 5.72c0-0.12-0.225-0.22-0.5-0.22S13 5.739 13 6.03zm20.54 0.173c-0.415 0.5-0.424 0.753-0.031 0.885C34.584 7.444 29.094 22 27.884 22c-0.223 0-1.383-0.9-2.576-2-3.16-2.913-3.467-2.449-0.421 0.636l2.603 2.635-1.295 1.398c-1.703 1.835-1.092 2.398 0.719 0.663 1.881-1.802 5.678-9.57 7.284-14.905C34.914 8.05 35.5 5.97 35.5 5.804c0-0.58-1.382-0.298-1.96 0.399zM2.876 6.33c0.344 0.139 0.906 0.139 1.25 0S4.188 6.079 3.5 6.079 2.531 6.193 2.875 6.331zM5 6.5C5 6.775 5.225 7 5.5 7S6 6.775 6 6.5 5.775 6 5.5 6 5 6.225 5 6.5zm10.5 0.25C15.5 6.888 15.613 7 15.75 7S16 6.888 16 6.75 15.887 6.5 15.75 6.5 15.5 6.612 15.5 6.75zM13 7.25c0 0.138 0.113 0.25 0.25 0.25s0.25-0.112 0.25-0.25S13.387 7 13.25 7 13 7.112 13 7.25zM8.5 8.146C6.987 8.373 5.02 8.447 4.128 8.311 3.098 8.155 2.245 8.327 1.79 8.783 1.395 9.177 0.83 9.5 0.535 9.5 0.241 9.5 0 10.063 0 10.75 0 11.943 0.136 12 2.966 12c2.761 0 3.044 0.11 4.096 1.587 0.656 0.922 1.216 1.329 1.336 0.971 0.112-0.338-0.191-1.053-0.674-1.587-0.484-0.534-0.788-0.983-0.676-0.998 0.111-0.015 1.954-0.21 4.096-0.436 2.142-0.224 3.995-0.51 4.12-0.634 0.124-0.124-0.05-0.81-0.387-1.524-0.723-1.532-2.324-1.841-6.377-1.233zM16 7.75C16 7.888 16.113 8 16.25 8s0.25-0.112 0.25-0.25-0.113-0.25-0.25-0.25S16 7.612 16 7.75zm12.278 2.778c-1.78 3.966-1.386 4.36 0.472 0.472 0.785-1.643 1.308-3.108 1.162-3.255-0.146-0.146-0.882 1.106-1.634 2.783zM13.334 8.834c-0.184 0.183-0.484 0.183-0.668 0C12.483 8.65 12.633 8.5 13 8.5s0.517 0.15 0.334 0.334zm4.968 0.541c-1.22 4.022-1.857 8.726-1.305 9.62 0.555 0.898 1.384-2.032 1.761-6.225 0.172-1.915 0.423-3.658 0.557-3.875 0.134-0.217 0.021-0.395-0.252-0.395-0.272 0-0.615 0.394-0.761 0.875zM4.5 9.25c0 0.137-0.112 0.25-0.25 0.25S4 9.387 4 9.25 4.112 9 4.25 9 4.5 9.113 4.5 9.25zm2 0c0 0.137-0.112 0.25-0.25 0.25S6 9.387 6 9.25 6.112 9 6.25 9 6.5 9.113 6.5 9.25zm4.375 0.108c-0.756 0.114-1.993 0.114-2.75 0C7.369 9.243 7.987 9.15 9.5 9.15c1.512 0 2.132 0.093 1.375 0.208zM22.08 11.93c-0.698 2.33-0.737 3.226-0.123 2.847 0.494-0.305 1.282-4.204 0.923-4.564-0.121-0.12-0.481 0.652-0.8 1.717zM3.5 13.5c0 0.367 0.15 0.517 0.333 0.334 0.183-0.184 0.183-0.484 0-0.668C3.65 12.983 3.5 13.133 3.5 13.5zm1-0.25c0 0.137 0.112 0.25 0.25 0.25S5 13.387 5 13.25 4.888 13 4.75 13 4.5 13.113 4.5 13.25zm28.334 2.584c-0.184 0.183-0.334 0.565-0.334 0.85 0 0.313 0.233 0.283 0.592-0.076 0.325-0.325 0.475-0.708 0.333-0.85-0.142-0.142-0.408-0.108-0.591 0.076zM15.5 16.25c0 0.137 0.113 0.25 0.25 0.25S16 16.387 16 16.25 15.887 16 15.75 16s-0.25 0.113-0.25 0.25zm3.959 2.375c-0.023 0.344-0.137 1.2-0.254 1.902-0.156 0.938 0.188 1.662 1.291 2.719 1.452 1.391 2.13 3.2 0.919 2.452-0.399-0.247-0.479-0.085-0.25 0.511 0.184 0.48 0.335 1.305 0.335 1.832 0 0.527 0.242 0.959 0.538 0.959 0.296 0 0.497-0.619 0.447-1.375-0.139-2.099-0.066-2.203 1.091-1.584 0.928 0.497 1.031 0.46 0.75-0.274C24.146 25.301 24 24.824 24 24.71c0-0.115-0.281-0.212-0.625-0.215-0.343-0.003-1.336-0.736-2.204-1.631-1.391-1.431-1.5-1.736-0.91-2.543 0.683-0.934 0.495-2.32-0.314-2.32-0.246 0-0.465 0.282-0.488 0.625zM6.75 21c-0.17 0.275-0.07 0.5 0.22 0.5 0.292 0 0.53-0.225 0.53-0.5s-0.099-0.5-0.22-0.5c-0.122 0-0.36 0.225-0.53 0.5zm9 0.09c-0.825 0.245-2.328 0.325-3.339 0.177-1.258-0.183-2.075-0.032-2.586 0.48-0.412 0.411-1.159 0.823-1.661 0.916C5.802 23.1 2.816 24.16 3.378 24.36c0.345 0.124 0.501 0.431 0.345 0.683C3.569 25.294 4.13 25.5 4.971 25.5 5.81 25.5 6.5 25.725 6.5 26s-0.956 0.503-2.125 0.508C2.819 26.514 2.45 26.645 3 27c0.413 0.267 1.699 0.488 2.86 0.492 1.7 0.006 2.05 0.158 1.81 0.788-0.165 0.428-0.6 0.663-0.968 0.521-0.369-0.14-0.803-0.041-0.965 0.222C5.574 29.285 5.932 29.5 6.532 29.5c0.856 0 0.97 0.145 0.534 0.671-0.715 0.861-0.279 1.291 0.977 0.963 0.696-0.183 0.898-0.534 0.688-1.196C8.528 29.304 8.676 29 9.186 29c0.415 0 0.892-0.223 1.06-0.494 0.175-0.284-0.026-0.367-0.471-0.197-1.216 0.467-0.908-0.593 0.349-1.199l1.125-0.543-1.125-0.033C8.85 26.496 8.62 25.753 9.75 25.319c0.412-0.158 0.75-0.621 0.75-1.03 0-0.658 2.703-1.778 4.31-1.786 0.585-0.003 1.464 4.422 1.266 6.372C16.014 29.494 16.197 30 16.482 30 16.791 30 17 28.576 17 26.47c0-2.225-0.184-3.415-0.5-3.22-0.786 0.485-0.585-0.328 0.33-1.338 1.061-1.173 0.792-1.377-1.08-0.822zm25.285 0.994c-0.019 0.459-0.171 1.19-0.338 1.625-0.2 0.519-0.028 0.791 0.5 0.791 0.875 0 0.97-0.698 0.303-2.25-0.343-0.799-0.437-0.832-0.465-0.166zM10.5 22.25c0 0.137-0.113 0.25-0.25 0.25S10 22.387 10 22.25 10.113 22 10.25 22s0.25 0.113 0.25 0.25zM39 23.5c0 0.366 0.15 0.517 0.334 0.334 0.182-0.184 0.182-0.484 0-0.668C39.15 22.983 39 23.134 39 23.5zM8.634 24.328c-0.338 0.137-1.013 0.146-1.5 0.018-0.486-0.127-0.209-0.238 0.616-0.248 0.825-0.01 1.223 0.093 0.884 0.23zM5 24.75C5 24.887 4.888 25 4.75 25S4.5 24.887 4.5 24.75s0.112-0.25 0.25-0.25S5 24.613 5 24.75zm26.5 0.75c0 0.275 0.238 0.5 0.53 0.5 0.29 0 0.39-0.225 0.22-0.5S31.842 25 31.72 25c-0.121 0-0.22 0.225-0.22 0.5zm6.822 0.54c-0.857 0.826-0.87 1.012-0.164 2.427C38.58 29.311 38.74 30 38.517 30c-0.223 0-0.375 0.394-0.337 0.875 0.048 0.605-0.355 0.924-1.305 1.033-0.86 0.099-1.375-0.079-1.375-0.475 0-0.465-0.148-0.485-0.556-0.076C33.995 32.303 34.96 33 37.225 33c1.465 0 2.26-0.243 2.455-0.75 0.158-0.413 0.547-0.75 0.864-0.75 0.416 0 0.404 0.21-0.045 0.75-0.52 0.627-0.448 0.75 0.439 0.75C41.717 33 42 32.689 42 31.834c0-1.512-0.486-1.968-1.341-1.258-0.852 0.706-1.284 0.331-0.867-0.755C39.965 29.37 40.532 29 41.053 29c1.19 0 1.244-0.85 0.072-1.155-0.834-0.218-0.834-0.232 0-0.287C42.063 27.496 42.429 25 41.5 25c-0.275 0-0.5 0.225-0.5 0.5 0 0.609-1.35 0.67-1.584 0.073-0.091-0.236-0.583-0.026-1.094 0.466zM8 25.72c0 0.122-0.225 0.36-0.5 0.53C7.225 26.42 7 26.32 7 26.03c0-0.292 0.225-0.53 0.5-0.53S8 25.599 8 25.72zm15 1.03c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25-0.113-0.25-0.25-0.25S23 26.613 23 26.75zM1.5 27.5c0 0.366 0.15 0.517 0.333 0.334 0.184-0.184 0.184-0.484 0-0.668C1.65 26.983 1.5 27.134 1.5 27.5zm23.532 0.29c0.325 0.39 0.913 0.71 1.308 0.71 1.006 0 0.39-0.823-0.861-1.15-0.85-0.223-0.93-0.144-0.447 0.44zm-10.28 1.335c-0.18 0.894-0.429 1.992-0.554 2.441-0.126 0.448 0.06 0.926 0.412 1.06 0.465 0.178 0.667-0.492 0.736-2.44 0.11-3.047-0.114-3.446-0.594-1.061zm8.341 0.282c-0.078 1.37 0.023 1.65 0.393 1.093 0.603-0.907 0.664-2.349 0.114-2.688-0.22-0.136-0.448 0.582-0.507 1.595zM24.5 29.25c0 0.137 0.113 0.25 0.25 0.25S25 29.387 25 29.25 24.887 29 24.75 29s-0.25 0.113-0.25 0.25zm1.166 0.084c0.184 0.183 0.484 0.183 0.668 0C26.517 29.15 26.366 29 26 29s-0.517 0.15-0.334 0.334zM28 29.75c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25-0.113-0.25-0.25-0.25S28 29.613 28 29.75zM1 30.5c0 0.366 0.15 0.517 0.333 0.334 0.183-0.184 0.183-0.484 0-0.668C1.15 29.983 1 30.134 1 30.5zm28-0.25c0 0.137 0.113 0.25 0.25 0.25s0.25-0.113 0.25-0.25S29.387 30 29.25 30 29 30.113 29 30.25zm-7.5 1.5c0 0.137 0.113 0.25 0.25 0.25S22 31.887 22 31.75s-0.113-0.25-0.25-0.25-0.25 0.113-0.25 0.25zm-14 0.75c0.982 0.635 2.5 0.635 2.5 0 0-0.275-0.731-0.496-1.625-0.492C7.142 32.013 6.931 32.132 7.5 32.5zm17.5 0c0.97 0.627 1.5 0.627 1.5 0 0-0.275-0.506-0.496-1.125-0.492-0.91 0.006-0.982 0.1-0.375 0.492z" android:fillType="evenOdd">
-            <aapt:attr name="android:fillColor">
-                <gradient android:startY="1.5" android:startX="32.5" android:endY="37.0292" android:endX="-0.969948" android:type="linear">
-                    <item android:offset="0" android:color="#FFECE3DA"/>
-                    <item android:offset="0.208782" android:color="#FFEBE2D6"/>
-                    <item android:offset="1" android:color="#FFEBE2D9"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-    </group>
-</vector>
diff --git a/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml b/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml
deleted file mode 100644
index 15567605..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/plus_sign.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="40" android:viewportWidth="40" android:width="24dp">
-    <path android:fillColor="#999999" android:pathData="M21.5 14c0-0.828-0.672-1.5-1.5-1.5s-1.5 0.672-1.5 1.5v4.5H14c-0.828 0-1.5 0.672-1.5 1.5s0.672 1.5 1.5 1.5h4.5V26c0 0.828 0.672 1.5 1.5 1.5s1.5-0.672 1.5-1.5v-4.5H26c0.828 0 1.5-0.672 1.5-1.5s-0.672-1.5-1.5-1.5h-4.5V14z"/>
-</vector>
diff --git a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png b/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png
deleted file mode 100644
index e69de29..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-normal.png
+++ /dev/null
diff --git a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png b/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png
deleted file mode 100644
index e69de29..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/rendering-mode-shrink.png
+++ /dev/null
diff --git a/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png b/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png
deleted file mode 100644
index e3da5cb..0000000
--- a/external/paparazzi/paparazzi/src/test/resources/without-layout-params.png
+++ /dev/null
Binary files differ
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b276acff..e35bc05 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -56,8 +56,6 @@
 mockito = "2.25.0"
 moshi = "1.13.0"
 protobuf = "3.22.3"
-paparazzi = "1.0.0"
-paparazziNative = "2022.1.1-canary-f5f9f71"
 skiko = "0.7.7"
 spdxGradlePlugin = "0.3.0"
 sqldelight = "1.3.0"
@@ -245,11 +243,6 @@
 playServicesDevicePerformance = { module = "com.google.android.gms:play-services-deviceperformance", version = "16.0.0" }
 playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.1.0"}
 playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
-paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
-paparazziNativeJvm = { module = "app.cash.paparazzi:layoutlib-native-jdk11", version.ref = "paparazziNative" }
-paparazziNativeLinuxX64 = { module = "app.cash.paparazzi:layoutlib-native-linux", version.ref = "paparazziNative" }
-paparazziNativeMacOsArm64 = { module = "app.cash.paparazzi:layoutlib-native-macarm", version.ref = "paparazziNative" }
-paparazziNativeMacOsX64 = { module = "app.cash.paparazzi:layoutlib-native-macosx", version.ref = "paparazziNative" }
 protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
 protobufCompiler = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
 protobufGradlePlugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.9.4" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 4061606..82e12f6 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -238,7 +238,6 @@
             <trusting group="com.google.auto.value"/>
             <trusting group="com.google.auto.value" name="auto-value"/>
          </trusted-key>
-         <trusted-key id="70731C2FFB496DC4E8105AA3604F437C1682DDE5" group="app.cash.paparazzi"/>
          <trusted-key id="70CD19BFD9F6C330027D6F260315BFB7970A144F">
             <trusting group="com.sun.istack"/>
             <trusting group="com.sun.xml.bind"/>
diff --git a/settings.gradle b/settings.gradle
index ac76bab..7c7abd3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1113,7 +1113,6 @@
 includeProject(":internal-testutils-macrobenchmark", "testutils/testutils-macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":internal-testutils-paparazzi", "testutils/testutils-paparazzi", [BuildType.COMPOSE])
 includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.TOOLS])
 includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":kruth:kruth", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP, BuildType.COMPOSE])
@@ -1146,8 +1145,6 @@
 includeProject(":external:libyuv", [BuildType.CAMERA])
 includeProject(":noto-emoji-compat-font", new File(externalRoot, "noto-fonts/emoji-compat"), [BuildType.MAIN])
 includeProject(":noto-emoji-compat-flatbuffers", new File(externalRoot, "noto-fonts/emoji-compat-flatbuffers"), [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":external:paparazzi:paparazzi", [BuildType.COMPOSE])
-includeProject(":external:paparazzi:paparazzi-agent", [BuildType.COMPOSE])
 
 if (isAllProjects()) {
     includeProject(":docs-tip-of-tree")
diff --git a/testutils/testutils-paparazzi/build.gradle b/testutils/testutils-paparazzi/build.gradle
deleted file mode 100644
index 68d62b0..0000000
--- a/testutils/testutils-paparazzi/build.gradle
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 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.
- */
-
-/**
- * This file was created using the `create_project.py` script located in the
- * `<AndroidX root>/development/project-creator` directory.
- *
- * Please use that script when creating a new project, rather than copying an existing project and
- * modifying its settings.
- */
-import androidx.build.BundleInsideHelper
-import androidx.build.LibraryType
-
-plugins {
-    id("AndroidXPlugin")
-    id("kotlin")
-}
-
-BundleInsideHelper.forInsideJar(
-        project,
-        "com.google.protobuf",
-        // b/268288856 untangle this mess.
-        // Currently this has to match test/screenshot/screenshot/build.gradle because sometimes
-        // both :internal-testutils-paparazzi and :test:screenshot:screenshot are added to the
-        // classpath and picking a different package will cause missing class exceptions due to
-        // class shadowing
-        "androidx.test.screenshot.protobuf",
-        // proto-lite dependency includes .proto files, which are not used and would clash if
-        // users also use proto library directly
-        /* dropResourcesWithSuffix = */ ".proto"
-)
-
-dependencies {
-    api(project(":external:paparazzi:paparazzi"))
-    bundleInside(project(path: ":test:screenshot:screenshot-proto"))
-
-    testImplementation(libs.junit)
-    testImplementation(libs.kotlinTestJunit)
-}
-
-androidx {
-    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
-}
\ No newline at end of file
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt
deleted file mode 100644
index 510b7f2..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/AndroidXPaparazzi.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 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 androidx.testutils.paparazzi
-
-import app.cash.paparazzi.DeviceConfig
-import app.cash.paparazzi.Environment
-import app.cash.paparazzi.Paparazzi
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode
-import java.io.File
-
-/**
- * Creates a [Paparazzi] test rule configured from system properties for AndroidX tests with the
- * `AndroidXPaparazziPlugin` Gradle plugin applied.
- *
- * Golden images used with this framework are expected to have a one-to-one relationship to test
- * functions. This helps ensure isolation between test functions and facilitates updating golden
- * images programmatically via the `:updateGolden` Gradle task or CI.
- *
- * To this end, golden images are named by the qualified name of their test function, instead of
- * a secondary identifier. Additionally, the returned [Paparazzi] instance will enforce a limit of
- * one snapshot per test function.
- */
-fun androidxPaparazzi(
-    deviceConfig: DeviceConfig = DeviceConfig.PIXEL_6.copy(softButtons = false),
-    theme: String = "android:Theme.Material.NoActionBar.Fullscreen",
-    renderingMode: RenderingMode = RenderingMode.SHRINK,
-    imageDiffer: ImageDiffer = ImageDiffer.MSSIMMatcher
-) = Paparazzi(
-    deviceConfig = deviceConfig,
-    theme = theme,
-    renderingMode = renderingMode,
-    environment = Environment(
-        platformDir = systemProperty("platformDir"),
-        resDir = systemProperty("resDir"),
-        assetsDir = systemProperty("assetsDir"),
-        compileSdkVersion = systemProperty("compileSdkVersion").toInt(),
-        resourcePackageNames = systemProperty("resourcePackageNames").split(","),
-        appTestDir = System.getProperty("user.dir")!!
-    ),
-    snapshotHandler = GoldenVerifier(
-        modulePath = systemProperty("modulePath"),
-        goldenRootDirectory = File(systemProperty("goldenRootDir")),
-        reportDirectory = File(systemProperty("reportDir")),
-        imageDiffer = imageDiffer
-    )
-)
-
-/** Package name used for resolving system properties */
-internal const val PACKAGE_NAME = "androidx.testutils.paparazzi"
-
-/** Name of the internal Gradle plugin */
-private const val PLUGIN_NAME = "AndroidXPaparazziPlugin"
-
-/** Name of the module containing this library */
-private const val MODULE_NAME = ":internal-testutils-paparazzi"
-
-/** Read a system property with [PACKAGE_NAME] prefix, throwing an exception if missing */
-private fun systemProperty(name: String) = checkNotNull(System.getProperty("$PACKAGE_NAME.$name")) {
-    if (System.getProperty("$PACKAGE_NAME.gradlePluginApplied").toBoolean()) {
-        "Missing required system property: $PACKAGE_NAME.$name. This is likely due to version " +
-            "mismatch between $PLUGIN_NAME and $MODULE_NAME."
-    } else {
-        "Paparazzi system properties not set. Please ensure $PLUGIN_NAME is applied to your build."
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt
deleted file mode 100644
index 8282bd9d..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/GoldenVerifier.kt
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 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 androidx.testutils.paparazzi
-
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult.Status
-import app.cash.paparazzi.Snapshot
-import app.cash.paparazzi.SnapshotHandler
-import app.cash.paparazzi.TestName
-import java.awt.image.BufferedImage
-import java.io.File
-import javax.imageio.ImageIO
-
-/**
- * This [SnapshotHandler] implements AndroidX-specific logic for screenshot testing. Specifically,
- * it writes a report to [reportDirectory] at every comparison just before throwing an exception,
- * even on successful comparison. The report contains text and binary protos containing the status
- * of the comparison and relative paths to actual, expected (copy of golden), and difference
- * (magenta highlights) images as applicable. CI consumes these reports to allow updating golden
- * images from the web UI. Golden images can also be updated by the `:updateGolden` Gradle task,
- * which copies actual images to their expected location in the golden repo.
- *
- * It also knows that the AndroidX golden repo, located at [goldenRootDirectory], is partitioned
- * by Gradle project path or [modulePath] and loads images from it.
- *
- * As noted in documentation on [androidxPaparazzi], the verifier is the component to enforce a
- * one-to-one relationship between test functions and golden images. This isn't strictly necessary,
- * but CI currently expects reports to be generated as a side effect of a regular test invocation.
- * Enforcing a one-to-one relationship avoids a situation where a failing assertion earlier in the
- * test would stop a later assertion from executing and generating its report as a side effect,
- * requiring multiple rounds of updating golden images to get the test passing.
- *
- * Additionally, CI currently only supports one comparison per report (note the [ScreenshotResult]
- * proto is not repeated). If this limit is lifted in the future, we would need to devise a scheme
- * for naming additional golden images such as an index of the order they were invoked and wrap the
- * test rule with something like an `ErrorCollector` to ensure later assertions are always
- * reachable.
- *
- * @property modulePath Unique path for the module, derived from Gradle project path. The verifier
- * will search for golden images in this directory relative to [goldenRootDirectory].
- *
- * @property goldenRootDirectory Location on disk of the golden images repo. Golden images for this
- * module are found in the [modulePath] directory under this directory.
- *
- * @property reportDirectory Directory to write reports, including protos, actual and expected
- * images, and image diffs.
- *
- * @property imageDiffer An [ImageDiffer] for comparing images.
- *
- * @property goldenRepoName Name of the repo containing golden images. Used for CI
- */
-internal class GoldenVerifier(
-    val modulePath: String,
-    val goldenRootDirectory: File,
-    val reportDirectory: File,
-    val imageDiffer: ImageDiffer = ImageDiffer.PixelPerfect,
-    val goldenRepoName: String = ANDROIDX_GOLDEN_REPO_NAME
-) : SnapshotHandler {
-    /** Directory containing golden images for this module. */
-    val goldenDirectory = goldenRootDirectory.resolve(modulePath)
-
-    /** The set of tests seen by this verifier, used to enforce single assertion per test. */
-    private val attemptedTests = mutableSetOf<TestName>()
-
-    /**
-     * Asserts that the [actual] matches the expected golden for the test described in [snapshot].
-     * As a side effect, this writes the report proto, actual, expected, and difference images to
-     * [reportDirectory] as appropriate.
-     */
-    fun assertMatchesGolden(snapshot: Snapshot, actual: BufferedImage) {
-        check(snapshot.testName !in attemptedTests) {
-            "Snapshot already taken for test \"${snapshot.testName.methodName}\". Taking " +
-                "multiple snapshots per test is not currently supported."
-        }
-        attemptedTests += snapshot.testName
-
-        val expected = snapshot.toGoldenFile().takeIf { it.canRead() }?.let { ImageIO.read(it) }
-        val analysis = analyze(expected, actual)
-
-        fun updateMessage() = "To update golden images for this test module, run " +
-            "${updateGoldenGradleCommand()}."
-
-        writeReport(snapshot, analysis)
-
-        when (analysis) {
-            is AnalysisResult.Passed -> { /** Test passed, don't need to throw anything */ }
-            is AnalysisResult.Failed -> throw AssertionError(
-                "Actual image differs from golden image: ${analysis.imageDiff.description}. " +
-                    updateMessage()
-            )
-            is AnalysisResult.SizeMismatch -> throw AssertionError(
-                "Actual image has different dimensions than golden image. " +
-                    "Actual: ${analysis.actual.width}x${analysis.actual.height}. " +
-                    "Golden: ${analysis.expected.width}x${analysis.expected.height}. " +
-                    updateMessage()
-            )
-            is AnalysisResult.MissingGolden -> throw AssertionError(
-                "Expected golden image for test \"${snapshot.testName.methodName}\" does not " +
-                    "exist. Run ${updateGoldenGradleCommand()} " +
-                    "to create it and update all golden images for this test module."
-            )
-        }
-    }
-
-    /** Compare [expected] golden image to [actual] image and return an [AnalysisResult] */
-    fun analyze(expected: BufferedImage?, actual: BufferedImage): AnalysisResult {
-        if (expected == null) {
-            return AnalysisResult.MissingGolden(actual)
-        }
-
-        if (actual.width != expected.width || actual.height != expected.height) {
-            return AnalysisResult.SizeMismatch(actual, expected)
-        }
-
-        return when (val diff = imageDiffer.diff(actual, expected)) {
-            is ImageDiffer.DiffResult.Similar -> AnalysisResult.Passed(actual, expected, diff)
-            is ImageDiffer.DiffResult.Different -> AnalysisResult.Failed(actual, expected, diff)
-        }
-    }
-
-    /**
-     * Write the [analysis] for test described by [snapshot] to [reportDirectory] as both binary
-     * and text proto, including actual, expected, and difference image files as appropriate.
-     */
-    fun writeReport(snapshot: Snapshot, analysis: AnalysisResult) {
-        val actualFile = snapshot.toActualFile().also { ImageIO.write(analysis.actual, "PNG", it) }
-        val goldenFile = snapshot.toGoldenFile()
-
-        val resultProto = ScreenshotResult.newBuilder().apply {
-            currentScreenshotFileName = actualFile.toRelativeString(reportDirectory)
-            repoRootPath = goldenRepoName
-            locationOfGoldenInRepo = goldenFile.toRelativeString(goldenRootDirectory)
-        }
-
-        fun diffFile(diff: BufferedImage) = snapshot.toDiffFile()
-            .also { ImageIO.write(diff, "PNG", it) }
-            .toRelativeString(reportDirectory)
-
-        fun expectedFile() = goldenFile.copyTo(snapshot.toExpectedFile())
-            .toRelativeString(reportDirectory)
-
-        when (analysis) {
-            is AnalysisResult.Passed -> resultProto.apply {
-                result = Status.PASSED
-                expectedImageFileName = expectedFile()
-                analysis.imageDiff.highlights?.let { diffImageFileName = diffFile(it) }
-                comparisonStatistics = analysis.imageDiff.taggedDescription()
-            }
-            is AnalysisResult.Failed -> resultProto.apply {
-                result = Status.FAILED
-                expectedImageFileName = expectedFile()
-                diffImageFileName = diffFile(analysis.imageDiff.highlights)
-                comparisonStatistics = analysis.imageDiff.taggedDescription()
-            }
-            is AnalysisResult.SizeMismatch -> resultProto.apply {
-                result = Status.SIZE_MISMATCH
-                expectedImageFileName = expectedFile()
-            }
-            is AnalysisResult.MissingGolden -> resultProto.apply {
-                result = Status.MISSING_GOLDEN
-            }
-        }
-
-        val result = resultProto.build()
-        snapshot.toResultProtoFile().outputStream().use { result.writeTo(it) }
-
-        // TODO(b/244200590): Remove text proto output, or replace with JSON
-        snapshot.toResultTextProtoFile().writeText(result.toString())
-    }
-
-    /**
-     * Analysis result ADT returned from [analyze], including actual and expected images and
-     * an [ImageDiffer.DiffResult].
-     */
-    sealed interface AnalysisResult {
-        val actual: BufferedImage
-
-        data class Passed(
-            override val actual: BufferedImage,
-            val expected: BufferedImage,
-            val imageDiff: ImageDiffer.DiffResult.Similar
-        ) : AnalysisResult
-
-        data class Failed(
-            override val actual: BufferedImage,
-            val expected: BufferedImage,
-            val imageDiff: ImageDiffer.DiffResult.Different
-        ) : AnalysisResult
-
-        data class SizeMismatch(
-            override val actual: BufferedImage,
-            val expected: BufferedImage
-        ) : AnalysisResult
-
-        data class MissingGolden(
-            override val actual: BufferedImage
-        ) : AnalysisResult
-    }
-
-    override fun newFrameHandler(
-        snapshot: Snapshot,
-        frameCount: Int,
-        fps: Int
-    ): SnapshotHandler.FrameHandler {
-        require(frameCount == 1) { "Videos are not yet supported" }
-
-        return object : SnapshotHandler.FrameHandler {
-            override fun handle(image: BufferedImage) {
-                assertMatchesGolden(snapshot, image)
-            }
-
-            override fun close() = Unit
-        }
-    }
-
-    override fun close() = Unit
-
-    /** Adds [ImageDiffer.name] as a prefix to [ImageDiffer.DiffResult.description]. */
-    private fun ImageDiffer.DiffResult.taggedDescription() =
-        "[${imageDiffer.name}]: $description"
-
-    // Filename templates based for a given snapshot
-    private fun Snapshot.toGoldenFile() = goldenDirectory.resolve("${toFileName()}_paparazzi.png")
-    private fun Snapshot.toExpectedFile() = reportDirectory.resolve("${toFileName()}_expected.png")
-    private fun Snapshot.toActualFile() = reportDirectory.resolve("${toFileName()}_actual.png")
-    private fun Snapshot.toDiffFile() = reportDirectory.resolve("${toFileName()}_diff.png")
-    private fun Snapshot.toResultProtoFile() =
-        reportDirectory.resolve("${toFileName()}_goldResult.pb")
-    private fun Snapshot.toResultTextProtoFile() =
-        reportDirectory.resolve("${toFileName()}_goldResult.textproto")
-
-    companion object {
-        /** Name of the AndroidX golden repo. */
-        const val ANDROIDX_GOLDEN_REPO_NAME = "platform/frameworks/support-golden"
-
-        /** Name of the updateGolden Gradle command for this module, via system properties. */
-        fun updateGoldenGradleCommand() =
-            "./gradlew ${
-                (System.getProperty("$PACKAGE_NAME.updateGoldenTask") ?: ":updateGolden")
-            } -Pandroidx.ignoreTestFailures=true"
-
-        /** Render test function name as a fully qualified string. */
-        fun TestName.toQualifiedName(): String {
-            return if (packageName.isEmpty()) {
-                "$className.$methodName"
-            } else {
-                "$packageName.$className.$methodName"
-            }
-        }
-
-        /** Get a file name with special characters removed from a snapshot. */
-        fun Snapshot.toFileName() = testName.toQualifiedName().replace(Regex("\\W+"), "_")
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt b/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt
deleted file mode 100644
index 8902cb4..0000000
--- a/testutils/testutils-paparazzi/src/main/kotlin/androidx/testutils/paparazzi/ImageDiffer.kt
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Copyright 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 androidx.testutils.paparazzi
-
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Similar
-import java.awt.image.BufferedImage
-import kotlin.math.pow
-
-@JvmDefaultWithCompatibility
-/**
- *  Functional interface to compare two images and returns a [ImageDiffer.DiffResult] ADT containing
- *  comparison statistics and a difference image, if applicable.
- */
-fun interface ImageDiffer {
-    /**
-     * Compare image [a] to image [b]. Implementations may assume [a] and [b] have the same
-     * dimensions.
-     */
-    fun diff(a: BufferedImage, b: BufferedImage): DiffResult
-
-    /** A name to be used in logs for this differ, defaulting to the class's simple name. */
-    val name
-        get() = requireNotNull(this::class.simpleName) {
-            "Could not determine ImageDiffer.name reflectively. Please override ImageDiffer.name."
-        }
-
-    /**
-     * Result ADT returned from [diff].
-     *
-     * A differ may permit a small amount of difference, even for [Similar] results. Similar results
-     * must include a [description], even if it's trivial, but may omit the [highlights] image if
-     * it would be fully transparent.
-     *
-     * @property description A human-readable description of how the images differed, such as the
-     * count of different pixels or percentage changed. Displayed in test failure messages and in
-     * CI.
-     *
-     * @property highlights An image with a transparent background, highlighting where the compared
-     * images differ, typically in shades of magenta. Displayed in CI.
-     */
-    sealed interface DiffResult {
-        val description: String
-        val highlights: BufferedImage?
-
-        data class Similar(
-            override val description: String,
-            override val highlights: BufferedImage? = null
-        ) : DiffResult
-
-        data class Different(
-            override val description: String,
-            override val highlights: BufferedImage
-        ) : DiffResult
-    }
-
-    /**
-     * Pixel perfect image differ requiring images to be identical.
-     *
-     * The alpha channel is treated as pre-multiplied, meaning RGB channels may differ if the alpha
-     * channel is 0 (fully transparent).
-     */
-    // TODO(b/244752233): Support wide gamut images.
-    object PixelPerfect : ImageDiffer {
-        override fun diff(a: BufferedImage, b: BufferedImage): DiffResult {
-            check(a.width == b.width && a.height == b.height) { "Images are different sizes" }
-            val width = a.width
-            val height = b.height
-            val highlights = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
-            var count = 0
-
-            for (x in 0 until width) {
-                for (y in 0 until height) {
-                    val aPixel = a.getRGB(x, y)
-                    val bPixel = b.getRGB(x, y)
-
-                    // Compare full ARGB pixels, but allow other channels to differ if alpha is 0
-                    if (aPixel == bPixel || (aPixel ushr 24 == 0 && bPixel ushr 24 == 0)) {
-                        highlights.setRGB(x, y, TRANSPARENT.toInt())
-                    } else {
-                        count++
-                        highlights.setRGB(x, y, MAGENTA.toInt())
-                    }
-                }
-            }
-
-            val description = "$count of ${width * height} pixels different"
-            return if (count > 0) {
-                DiffResult.Different(description, highlights)
-            } else {
-                DiffResult.Similar(description)
-            }
-        }
-    }
-
-    /**
-     * Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
-     * Simoncelli. Details can be read in their paper:
-     * https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
-     */
-    object MSSIMMatcher : ImageDiffer {
-        override fun diff(a: BufferedImage, b: BufferedImage): DiffResult {
-            val aIntArray = a.toIntArray()
-            val bIntArray = b.toIntArray()
-            val SSIMTotal = calculateSSIM(aIntArray, bIntArray, a.width, a.height)
-
-            val stats = "[MSSIM] Required SSIM: $SSIM_THRESHOLD, Actual " +
-                "SSIM: " + "%.3f".format(SSIMTotal)
-            if (SSIMTotal >= SSIM_THRESHOLD) {
-                return DiffResult.Similar(stats)
-            }
-            return PixelPerfect.diff(a, b)
-        }
-
-        internal fun calculateSSIM(
-            ideal: IntArray,
-            given: IntArray,
-            width: Int,
-            height: Int
-        ): Double {
-            return calculateSSIM(ideal, given, 0, width, width, height)
-        }
-
-        private fun calculateSSIM(
-            ideal: IntArray,
-            given: IntArray,
-            offset: Int,
-            stride: Int,
-            width: Int,
-            height: Int
-        ): Double {
-            var SSIMTotal = 0.0
-            var windows = 0
-            var currentWindowY = 0
-            while (currentWindowY < height) {
-                val windowHeight = computeWindowSize(currentWindowY, height)
-                var currentWindowX = 0
-                while (currentWindowX < width) {
-                    val windowWidth = computeWindowSize(currentWindowX, width)
-                    val start: Int =
-                        indexFromXAndY(currentWindowX, currentWindowY, stride, offset)
-                    if (isWindowWhite(ideal, start, stride, windowWidth, windowHeight) &&
-                        isWindowWhite(given, start, stride, windowWidth, windowHeight)
-                    ) {
-                        currentWindowX += WINDOW_SIZE
-                        continue
-                    }
-                    windows++
-                    val means =
-                        getMeans(ideal, given, start, stride, windowWidth, windowHeight)
-                    val meanX = means[0]
-                    val meanY = means[1]
-                    val variances = getVariances(
-                        ideal, given, meanX, meanY, start, stride,
-                        windowWidth, windowHeight
-                    )
-                    val varX = variances[0]
-                    val varY = variances[1]
-                    val stdBoth = variances[2]
-                    val SSIM = SSIM(meanX, meanY, varX, varY, stdBoth)
-                    SSIMTotal += SSIM
-                    currentWindowX += WINDOW_SIZE
-                }
-                currentWindowY += WINDOW_SIZE
-            }
-            if (windows == 0) {
-                return 1.0
-            }
-            return SSIMTotal / windows.toDouble()
-        }
-
-        /**
-         * Compute the size of the window. The window defaults to WINDOW_SIZE, but
-         * must be contained within dimension.
-         */
-        private fun computeWindowSize(coordinateStart: Int, dimension: Int): Int {
-            return if (coordinateStart + WINDOW_SIZE <= dimension) {
-                WINDOW_SIZE
-            } else {
-                dimension - coordinateStart
-            }
-        }
-
-        private fun isWindowWhite(
-            colors: IntArray,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): Boolean {
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    if (colors[indexFromXAndY(x, y, stride, start)] != WHITE) {
-                        return false
-                    }
-                }
-            }
-            return true
-        }
-
-        /**
-         * This calculates the position in an array that would represent a bitmap given the parameters.
-         */
-        private fun indexFromXAndY(x: Int, y: Int, stride: Int, offset: Int): Int {
-            return x + y * stride + offset
-        }
-
-        private fun SSIM(
-            muX: Double,
-            muY: Double,
-            sigX: Double,
-            sigY: Double,
-            sigXY: Double
-        ): Double {
-            var SSIM = (2 * muX * muY + CONSTANT_C1) * (2 * sigXY + CONSTANT_C2)
-            val denom = ((muX * muX + muY * muY + CONSTANT_C1) * (sigX + sigY + CONSTANT_C2))
-            SSIM /= denom
-            return SSIM
-        }
-
-        /**
-         * This method will find the mean of a window in both sets of pixels. The return is an array
-         * where the first double is the mean of the first set and the second double is the mean of the
-         * second set.
-         */
-        private fun getMeans(
-            pixels0: IntArray,
-            pixels1: IntArray,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): DoubleArray {
-            var avg0 = 0.0
-            var avg1 = 0.0
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    val index: Int = indexFromXAndY(x, y, stride, start)
-                    avg0 += getIntensity(pixels0[index])
-                    avg1 += getIntensity(pixels1[index])
-                }
-            }
-            avg0 /= windowWidth * windowHeight.toDouble()
-            avg1 /= windowWidth * windowHeight.toDouble()
-            return doubleArrayOf(avg0, avg1)
-        }
-
-        /**
-         * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
-         * return value is an array of doubles, the first is the variance of the first set of pixels,
-         * the second is the variance of the second set of pixels, and the third is the covariance.
-         */
-        private fun getVariances(
-            pixels0: IntArray,
-            pixels1: IntArray,
-            mean0: Double,
-            mean1: Double,
-            start: Int,
-            stride: Int,
-            windowWidth: Int,
-            windowHeight: Int
-        ): DoubleArray {
-            if (windowHeight == 1 && windowWidth == 1) {
-                // There is only one item. The variance of a single item would be 0.
-                // Since Bessel's correction is used below, it will return NaN instead of 0.
-                return doubleArrayOf(0.0, 0.0, 0.0)
-            }
-
-            var var0 = 0.0
-            var var1 = 0.0
-            var varBoth = 0.0
-            for (y in 0 until windowHeight) {
-                for (x in 0 until windowWidth) {
-                    val index: Int = indexFromXAndY(x, y, stride, start)
-                    val v0 = getIntensity(pixels0[index]) - mean0
-                    val v1 = getIntensity(pixels1[index]) - mean1
-                    var0 += v0 * v0
-                    var1 += v1 * v1
-                    varBoth += v0 * v1
-                }
-            }
-            // Using Bessel's correction. Hence, subtracting one.
-            val denominatorWithBesselsCorrection = windowWidth * windowHeight - 1.0
-            var0 /= denominatorWithBesselsCorrection
-            var1 /= denominatorWithBesselsCorrection
-            varBoth /= denominatorWithBesselsCorrection
-            return doubleArrayOf(var0, var1, varBoth)
-        }
-
-        /**
-         * Gets the intensity of a given pixel in RGB using luminosity formula
-         *
-         * l = 0.21R' + 0.72G' + 0.07B'
-         *
-         * The prime symbols dictate a gamma correction of 1.
-         */
-        private fun getIntensity(pixel: Int): Double {
-            val gamma = 1.0
-            var l = 0.0
-            l += 0.21f * (red(pixel) / 255f.toDouble()).pow(gamma)
-            l += 0.72f * (green(pixel) / 255f.toDouble()).pow(gamma)
-            l += 0.07f * (blue(pixel) / 255f.toDouble()).pow(gamma)
-            return l
-        }
-
-        /**
-         * Return the red component of a color int. This is the same as saying
-         * (color >> 16) & 0xFF
-         */
-        fun red(color: Int): Int {
-            return color shr 16 and 0xFF
-        }
-
-        /**
-         * Return the green component of a color int. This is the same as saying
-         * (color >> 8) & 0xFF
-         */
-        fun green(color: Int): Int {
-            return color shr 8 and 0xFF
-        }
-
-        /**
-         * Return the blue component of a color int. This is the same as saying
-         * color & 0xFF
-         */
-        fun blue(color: Int): Int {
-            return color and 0xFF
-        }
-
-        private fun BufferedImage.toIntArray(): IntArray {
-            val bitmapArray = IntArray(width * height)
-            for (x in 0 until width) {
-                for (y in 0 until height) {
-                    bitmapArray[y * width + x] = getRGB(x, y)
-                }
-            }
-            return bitmapArray
-        }
-    }
-
-    private companion object {
-        const val MAGENTA = 0xFF_FF_00_FFu
-        const val TRANSPARENT = 0x00_FF_FF_FFu
-        const val WHITE = 0xFFFFFF
-
-        // These values were taken from the publication
-        private const val CONSTANT_L = 254.0
-        private const val CONSTANT_K1 = 0.00001
-        private const val CONSTANT_K2 = 0.00003
-        private val CONSTANT_C1 = (CONSTANT_L * CONSTANT_K1).pow(2.0)
-        private val CONSTANT_C2 = (CONSTANT_L * CONSTANT_K2).pow(2.0)
-        private const val WINDOW_SIZE = 10
-
-        private val SSIM_THRESHOLD: Double = 0.98
-    }
-}
diff --git a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt b/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt
deleted file mode 100644
index aee8a81..0000000
--- a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/GoldenVerifierTest.kt
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright 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 androidx.testutils.paparazzi
-
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult
-import androidx.test.screenshot.proto.ScreenshotResultProto.ScreenshotResult.Status
-import app.cash.paparazzi.Snapshot
-import java.awt.image.BufferedImage
-import java.io.File
-import java.util.Date
-import javax.imageio.ImageIO
-import kotlin.test.Test
-import kotlin.test.assertContains
-import kotlin.test.assertEquals
-import kotlin.test.assertFails
-import kotlin.test.assertFailsWith
-import kotlin.test.assertIs
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
-import org.junit.rules.TestName
-
-class GoldenVerifierTest {
-    @get:Rule
-    val testName = TestName()
-
-    @get:Rule
-    val goldenDirectory = TemporaryFolder()
-
-    @get:Rule
-    val reportDirectory = TemporaryFolder()
-
-    private val modulePath = "testutils/testutils-paparazzi"
-
-    @Test
-    fun `snapshot handler success`() {
-        createGolden("circle")
-        goldenVerifier().newFrameHandler(snapshot(), 1, 0).handle(loadTestImage("circle"))
-    }
-
-    @Test
-    fun `snapshot handler failure`() {
-        createGolden("star")
-        assertFails {
-            goldenVerifier().newFrameHandler(snapshot(), 1, 0).handle(loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `removes special characters in file names`() {
-        createGolden("circle")
-        // Test that createGolden/goldenFile match naming in verifier
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-
-        assertEquals(
-            goldenFile().name,
-            "androidx_testutils_paparazzi_GoldenVerifierTest_" +
-                "removes_special_characters_in_file_names_paparazzi.png"
-        )
-    }
-
-    @Test
-    fun `writes report on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-
-        val proto = reportProto()
-        assertEquals(Status.PASSED, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("[PixelPerfect]: 0 of 65536 pixels different", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "PASSED")
-    }
-
-    @Test
-    fun `writes actual image on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image on success`() {
-        createGolden("circle")
-        goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertEquals(loadTestImage("circle"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `analysis of success`() {
-        val analysis = goldenVerifier().analyze(loadTestImage("circle"), loadTestImage("circle"))
-        assertIs<GoldenVerifier.AnalysisResult.Passed>(analysis)
-        assertEquals(loadTestImage("circle"), analysis.actual)
-        assertEquals(loadTestImage("circle"), analysis.expected)
-    }
-
-    @Test
-    fun `asserts on failure`() {
-        createGolden("star")
-        val message = "Actual image differs from golden image: 17837 of 65536 pixels different. " +
-            "To update golden images for this test module, run ./gradlew :updateGolden " +
-            "-Pandroidx.ignoreTestFailures=true."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-
-        val proto = reportProto()
-        assertEquals(Status.FAILED, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals(reportFile("diff.png").name, proto.diffImageFileName)
-        assertEquals("[PixelPerfect]: 17837 of 65536 pixels different", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "FAILED")
-    }
-
-    @Test
-    fun `writes actual image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("star"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `writes diff image on failure`() {
-        createGolden("star")
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("PixelPerfect_diff"), reportFile("diff.png").readImage())
-    }
-
-    @Test
-    fun `analysis of failure`() {
-        val analysis = goldenVerifier().analyze(loadTestImage("circle"), loadTestImage("star"))
-        assertIs<GoldenVerifier.AnalysisResult.Failed>(analysis)
-        assertEquals(loadTestImage("star"), analysis.actual)
-        assertEquals(loadTestImage("circle"), analysis.expected)
-        assertEquals(loadTestImage("PixelPerfect_diff"), analysis.imageDiff.highlights)
-    }
-
-    @Test
-    fun `asserts on size mismatch`() {
-        createGolden("horizontal_rectangle")
-        val message = "Actual image has different dimensions than golden image. Actual: 72x128. " +
-            "Golden: 128x72. To update golden images for this test module, run ./gradlew " +
-            ":updateGolden -Pandroidx.ignoreTestFailures=true."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        val proto = reportProto()
-        assertEquals(Status.SIZE_MISMATCH, proto.result)
-        assertEquals(reportFile("expected.png").name, proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "SIZE_MISMATCH")
-    }
-
-    @Test
-    fun `writes actual image for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        assertEquals(loadTestImage("vertical_rectangle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `writes expected image for size mismatch`() {
-        createGolden("horizontal_rectangle")
-        assertFails {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("vertical_rectangle"))
-        }
-
-        assertEquals(loadTestImage("horizontal_rectangle"), reportFile("expected.png").readImage())
-    }
-
-    @Test
-    fun `analysis of size mismatch`() {
-        val analysis = goldenVerifier()
-            .analyze(loadTestImage("horizontal_rectangle"), loadTestImage("vertical_rectangle"))
-        assertIs<GoldenVerifier.AnalysisResult.SizeMismatch>(analysis)
-        assertEquals(loadTestImage("vertical_rectangle"), analysis.actual)
-        assertEquals(loadTestImage("horizontal_rectangle"), analysis.expected)
-    }
-
-    @Test
-    fun `asserts on missing golden`() {
-        val message = "Expected golden image for test \"asserts on missing golden\" does not " +
-            "exist. Run ./gradlew :updateGolden -Pandroidx.ignoreTestFailures=true to create it " +
-            "and update all golden images for this test module."
-
-        assertFailsWithMessage(message) {
-            goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        }
-    }
-
-    @Test
-    fun `writes result proto for missing golden`() {
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-
-        val proto = reportProto()
-        assertEquals(Status.MISSING_GOLDEN, proto.result)
-        assertEquals("", proto.expectedImageFileName)
-        assertEquals("", proto.diffImageFileName)
-        assertEquals("", proto.comparisonStatistics)
-        assertContains(reportFile("goldResult.textproto").readText(), "MISSING_GOLDEN")
-    }
-
-    @Test
-    fun `writes actual image for missing golden`() {
-        assertFails { goldenVerifier().assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-        assertEquals(loadTestImage("circle"), reportFile("actual.png").readImage())
-    }
-
-    @Test
-    fun `analysis of missing golden`() {
-        val analysis = goldenVerifier().analyze(null, loadTestImage("circle"))
-        assertIs<GoldenVerifier.AnalysisResult.MissingGolden>(analysis)
-        assertEquals(loadTestImage("circle"), analysis.actual)
-    }
-
-    @Test
-    fun `ensures single snapshot per method`() {
-        val verifier = goldenVerifier()
-        createGolden("circle")
-
-        verifier.assertMatchesGolden(snapshot(), loadTestImage("circle"))
-        assertFails { verifier.assertMatchesGolden(snapshot(), loadTestImage("circle")) }
-    }
-
-    private fun goldenVerifier() = GoldenVerifier(
-        modulePath = modulePath,
-        goldenRootDirectory = goldenDirectory.root,
-        reportDirectory = reportDirectory.root
-    )
-
-    /** Assert [block] throws an [AssertionError] with supplied [message]. */
-    private inline fun assertFailsWithMessage(message: String, block: () -> Unit) {
-        assertEquals(message, assertFailsWith<AssertionError> { block() }.message)
-    }
-
-    /** Compare two images using [ImageDiffer.PixelPerfect]. */
-    private fun assertEquals(expected: BufferedImage, actual: BufferedImage) {
-        assertIs<ImageDiffer.DiffResult.Similar>(
-            ImageDiffer.PixelPerfect.diff(expected, actual),
-            message = "Expected images to be identical, but they were not."
-        )
-    }
-
-    private fun snapshot() = Snapshot(
-        name = null,
-        testName = app.cash.paparazzi.TestName(
-            packageName = this::class.java.packageName,
-            className = this::class.simpleName!!,
-            methodName = testName.methodName
-        ),
-        timestamp = Date()
-    )
-
-    /** Create a golden image for this test from the supplied test image [name]. */
-    private fun createGolden(name: String) = javaClass.getResourceAsStream("$name.png")!!
-            .copyTo(goldenFile().apply { parentFile!!.mkdirs() }.outputStream())
-
-    /** Relative path to golden image for this test. */
-    private fun goldenPath() = "$modulePath/${testName()}_paparazzi.png"
-
-    /** Resolve the file path for a golden image for this test under [goldenDirectory]. */
-    private fun goldenFile() = goldenDirectory.root.resolve(goldenPath()).canonicalFile
-
-    /** Read the binary result proto under for this test and check common fields. */
-    private fun reportProto() =
-        ScreenshotResult.parseFrom(reportFile("goldResult.pb").inputStream()).also { proto ->
-            assertEquals(reportFile("actual.png").name, proto.currentScreenshotFileName)
-            assertEquals(GoldenVerifier.ANDROIDX_GOLDEN_REPO_NAME, proto.repoRootPath)
-            assertEquals(goldenPath(), proto.locationOfGoldenInRepo)
-        }
-
-    /** Resolve the file path for a report file with provided [suffix] under [reportDirectory]. */
-    private fun reportFile(suffix: String) =
-        reportDirectory.root.resolve("${testName()}_$suffix").canonicalFile
-
-    /** Convenience function to read an image from a file. */
-    private fun File.readImage() = ImageIO.read(this)
-
-    /** Fully qualified test ID with special characters replaced for this test. */
-    private fun testName() = "${this::class.qualifiedName!!}_${testName.methodName}"
-        .replace(Regex("\\W+"), "_")
-
-    /** Load a test image from resources. */
-    private fun loadTestImage(name: String) =
-        ImageIO.read(javaClass.getResourceAsStream("$name.png")!!)
-}
diff --git a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt b/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt
deleted file mode 100644
index 8b67607..0000000
--- a/testutils/testutils-paparazzi/src/test/kotlin/androidx/testutils/paparazzi/ImageDifferTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 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 androidx.testutils.paparazzi
-
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Different
-import androidx.testutils.paparazzi.ImageDiffer.DiffResult.Similar
-import androidx.testutils.paparazzi.ImageDiffer.PixelPerfect
-import javax.imageio.ImageIO
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertNull
-
-class ImageDifferTest {
-    @Test
-    fun `PixelPerfect similar`() {
-        val result = PixelPerfect.diff(loadTestImage("circle"), loadTestImage("circle"))
-        assertIs<Similar>(result)
-        assertEquals("0 of 65536 pixels different", result.description)
-        assertNull(result.highlights)
-    }
-
-    @Test
-    fun `PixelPerfect different`() {
-        val result = PixelPerfect.diff(loadTestImage("circle"), loadTestImage("star"))
-        assertIs<Different>(result)
-        assertEquals("17837 of 65536 pixels different", result.description)
-        assertIs<Similar>(
-            PixelPerfect.diff(result.highlights, loadTestImage("PixelPerfect_diff"))
-        )
-    }
-
-    @Test
-    fun `PixelPerfect name`() {
-        assertEquals("PixelPerfect", PixelPerfect.name)
-    }
-
-    private fun loadTestImage(name: String) =
-        ImageIO.read(javaClass.getResourceAsStream("$name.png")!!)
-}
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png
deleted file mode 100644
index 8e3b7b8..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/PixelPerfect_diff.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png
deleted file mode 100644
index e6d58321..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/circle.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png
deleted file mode 100644
index a13221a..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/horizontal_rectangle.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png
deleted file mode 100644
index 61b0c64..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/star.png
+++ /dev/null
Binary files differ
diff --git a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png b/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png
deleted file mode 100644
index 91a607e..0000000
--- a/testutils/testutils-paparazzi/src/test/resources/androidx/testutils/paparazzi/vertical_rectangle.png
+++ /dev/null
Binary files differ