From 21f9bcae5f02b4c38d27996d810c9c2c2ad9a568 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 1 May 2023 13:27:02 +0100 Subject: [PATCH] Update device definitions --- roborazzi/build.gradle | 6 + .../roborazzi/RobolectricDeviceQualifiers.kt | 62 +++--- .../com/github/takahirom/roborazzi/Devices.kt | 56 ++++++ .../roborazzi/GenerateQualifiersTest.kt | 179 +++++++++++------- scripts/download_device_xml.sh | 4 - 5 files changed, 213 insertions(+), 94 deletions(-) create mode 100644 roborazzi/src/test/java/com/github/takahirom/roborazzi/Devices.kt delete mode 100644 scripts/download_device_xml.sh diff --git a/roborazzi/build.gradle b/roborazzi/build.gradle index ff1d559d6..7922741ba 100644 --- a/roborazzi/build.gradle +++ b/roborazzi/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21' } // TODO: Use build-logic @@ -28,4 +29,9 @@ dependencies { api "com.dropbox.differ:differ:0.0.1-alpha1" api 'androidx.test.espresso:espresso-core:3.5.1' + + testImplementation "io.ktor:ktor-serialization-kotlinx-xml:2.3.0" + testImplementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.11" + testImplementation "com.squareup.okhttp3:okhttp-coroutines:5.0.0-alpha.11" + testImplementation("org.apache.commons:commons-compress:1.23.0") } \ No newline at end of file diff --git a/roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt b/roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt index b432a0c6f..a6a5dd0fb 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt @@ -2,29 +2,45 @@ package com.github.takahirom.roborazzi object RobolectricDeviceQualifiers { // Generated by [GenerateQualifiersTest.kt] - // Data from: AOSP https://android.googlesource.com/platform/tools/base/+/mirror-goog-studio-master-dev/sdklib/src/main/java/com/android/sdklib/devices -// FROM: tv.xml - const val AndroidTV1080p = "w960dp-h540dp-xlarge-long-notround-television-xhdpi-keyshidden-dpad" - const val AndroidTV720p = "w962dp-h541dp-xlarge-long-notround-television-tvdpi-keyshidden-dpad" + // Data from: AOSP https://android.googlesource.com/platform/tools/base/+archive/refs/heads/mirror-goog-studio-main/sdklib/src/main/resources/com/android/sdklib/devices.tar.gz - // FROM: devices.xml -// FROM: wear.xml - const val WearOSSquare = "w187dp-h187dp-small-long-round-watch-hdpi-keyshidden-nonav" - const val WearOSRound = "w213dp-h213dp-small-long-round-watch-hdpi-keyshidden-nonav" - const val WearOSRoundChin = "w240dp-h218dp-small-notlong-notround-watch-tvdpi-keyshidden-nonav" + // Type: android-automotive-playstore + const val Automotive1024plandscape = "w1024dp-h768dp-normal-notlong-notround-car-mdpi-keyshidden-nonav" - // FROM: nexus.xml - const val NexusOne = "w320dp-h533dp-normal-long-notround-any-hdpi-keyshidden-trackball" - const val Nexus7 = "w600dp-h960dp-large-notlong-notround-any-xhdpi-keyshidden-nonav" - const val Nexus9 = "w1024dp-h768dp-xlarge-notlong-notround-any-xhdpi-keyshidden-nonav" - const val PixelC = "w1280dp-h900dp-xlarge-notlong-notround-any-xhdpi-keyshidden-nonav" - const val PixelXL = "w411dp-h731dp-normal-notlong-notround-any-560dpi-keyshidden-nonav" - const val Pixel4 = "w393dp-h829dp-normal-long-notround-any-440dpi-keyshidden-nonav" - const val Pixel4XL = "w411dp-h869dp-normal-long-notround-any-560dpi-keyshidden-nonav" - const val Pixel4a = "w393dp-h851dp-normal-long-notround-any-440dpi-keyshidden-nonav" - const val Pixel5 = "w393dp-h851dp-normal-long-notround-any-440dpi-keyshidden-nonav" + // Type: android-desktop + const val SmallDesktop = "w1366dp-h768dp-xlarge-long-notround-any-mdpi-keyshidden-nonav" + const val MediumDesktop = "w1920dp-h1080dp-xlarge-long-notround-any-xhdpi-keyshidden-nonav" + const val LargeDesktop = "w1920dp-h1080dp-xlarge-long-notround-any-mdpi-keyshidden-nonav" - // FROM: automotive.xml - const val Automotive1024plandscape = - "w1024dp-h768dp-normal-notlong-notround-car-mdpi-keyshidden-nonav" -} + // Type: default + const val ResizableExperimental = "w411dp-h891dp-normal-notlong-notround-any-420dpi-keyshidden-nonav" + const val SmallPhone = "w360dp-h640dp-normal-long-notround-any-xhdpi-keyshidden-nonav" + const val MediumPhone = "w411dp-h914dp-normal-long-notround-any-420dpi-keyshidden-nonav" + const val MediumTablet = "w1280dp-h800dp-xlarge-notlong-notround-any-xhdpi-keyshidden-nonav" + const val NexusOne = "w320dp-h533dp-normal-long-notround-any-hdpi-keyshidden-trackball" + const val Nexus7 = "w600dp-h960dp-large-notlong-notround-any-xhdpi-keyshidden-nonav" + const val Nexus9 = "w1024dp-h768dp-xlarge-notlong-notround-any-xhdpi-keyshidden-nonav" + const val PixelC = "w1280dp-h900dp-xlarge-notlong-notround-any-xhdpi-keyshidden-nonav" + const val PixelXL = "w411dp-h731dp-normal-notlong-notround-any-560dpi-keyshidden-nonav" + const val Pixel4 = "w393dp-h829dp-normal-long-notround-any-440dpi-keyshidden-nonav" + const val Pixel4XL = "w411dp-h869dp-normal-long-notround-any-560dpi-keyshidden-nonav" + const val Pixel4a = "w393dp-h851dp-normal-long-notround-any-440dpi-keyshidden-nonav" + const val Pixel5 = "w393dp-h851dp-normal-long-notround-any-440dpi-keyshidden-nonav" + const val Pixel6 = "w411dp-h914dp-normal-long-notround-any-420dpi-keyshidden-nonav" + const val Pixel6Pro = "w411dp-h891dp-normal-long-notround-any-560dpi-keyshidden-nonav" + const val Pixel6a = "w411dp-h914dp-normal-long-notround-any-420dpi-keyshidden-nonav" + const val Pixel7Pro = "w411dp-h891dp-normal-long-notround-any-560dpi-keyshidden-nonav" + const val Pixel7 = "w411dp-h914dp-normal-long-notround-any-420dpi-keyshidden-nonav" + + // Type: android-tv + const val Television4K = "w960dp-h540dp-xlarge-long-notround-television-xxxhdpi-keyshidden-dpad" + const val Television1080p = "w960dp-h540dp-xlarge-long-notround-television-xhdpi-keyshidden-dpad" + const val Television720p = "w962dp-h541dp-large-long-notround-television-tvdpi-keyshidden-dpad" + + // Type: android-wear + const val WearOSLargeRound = "w227dp-h227dp-small-long-round-watch-xhdpi-keyshidden-nonav" + const val WearOSSmallRound = "w192dp-h192dp-small-long-round-watch-xhdpi-keyshidden-nonav" + const val WearOSRectangular = "w201dp-h238dp-small-long-notround-watch-xhdpi-keyshidden-nonav" + const val WearOSSquare = "w180dp-h180dp-small-long-round-watch-xhdpi-keyshidden-nonav" + +} \ No newline at end of file diff --git a/roborazzi/src/test/java/com/github/takahirom/roborazzi/Devices.kt b/roborazzi/src/test/java/com/github/takahirom/roborazzi/Devices.kt new file mode 100644 index 000000000..815b50b70 --- /dev/null +++ b/roborazzi/src/test/java/com/github/takahirom/roborazzi/Devices.kt @@ -0,0 +1,56 @@ +package com.github.takahirom.roborazzi + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("devices", namespace = "http://schemas.android.com/sdk/devices/1", prefix = "d") +data class Devices(val devices: List) + +@Serializable +@XmlSerialName("device", namespace = "http://schemas.android.com/sdk/devices/1", prefix = "d") +data class Device( + @XmlElement(value = true) + val name: String, + val hardware: Hardware, + @XmlElement(value = true) + @SerialName("tag-id") + val tagId: String?, +) + +@Serializable +@XmlSerialName("hardware", namespace = "http://schemas.android.com/sdk/devices/1", prefix = "d") +data class Hardware( + val screen: Screen, + @XmlElement(value = true) + val nav: String, +) + +@Serializable +@XmlSerialName("screen", namespace = "http://schemas.android.com/sdk/devices/1", prefix = "d") +data class Screen( + @XmlElement(value = true) + @SerialName("screen-size") + val screenSize: String, + @XmlElement(value = true) + @SerialName("screen-ratio") + val screenRatio: String, + @XmlElement(value = true) + @SerialName("pixel-density") + val pixelDensity: String, + @XmlElement(value = true) + val dimensions: Dimensions +) + +@Serializable +@XmlSerialName("dimensions", namespace = "http://schemas.android.com/sdk/devices/1", prefix = "d") +data class Dimensions( + @XmlElement(value = true) + @SerialName("x-dimension") + val xDimension: Int, + @XmlElement(value = true) + @SerialName("y-dimension") + val yDimension: Int +) \ No newline at end of file diff --git a/roborazzi/src/test/java/com/github/takahirom/roborazzi/GenerateQualifiersTest.kt b/roborazzi/src/test/java/com/github/takahirom/roborazzi/GenerateQualifiersTest.kt index ac0e7828a..5f92b3e06 100644 --- a/roborazzi/src/test/java/com/github/takahirom/roborazzi/GenerateQualifiersTest.kt +++ b/roborazzi/src/test/java/com/github/takahirom/roborazzi/GenerateQualifiersTest.kt @@ -1,49 +1,65 @@ package com.github.takahirom.roborazzi -import java.io.File -import javax.xml.parsers.DocumentBuilderFactory import kotlin.math.roundToInt +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.decodeFromString +import nl.adaptivity.xmlutil.serialization.DefaultXmlSerializationPolicy +import nl.adaptivity.xmlutil.serialization.XML +import nl.adaptivity.xmlutil.serialization.XmlConfig.Companion.IGNORING_UNKNOWN_CHILD_HANDLER +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.executeAsync +import okio.ByteString.Companion.readByteString +import okio.FileSystem +import okio.Path.Companion.toPath +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.junit.Ignore import org.junit.Test -import org.w3c.dom.Document import org.w3c.dom.Node import org.w3c.dom.NodeList + class GenerateQualifiersTest { - /** - * Before execute this. Please run `scripts/download_device_xml.sh` - */ + val devicesTarGz = + ("https://android.googlesource.com" + + "/platform/tools/base/+archive/refs/heads/mirror-goog-studio-main/sdklib/" + + "src/main/resources/com/android/sdklib/devices.tar.gz").toHttpUrl() + + val client = OkHttpClient() + + val xml = XML { + policy = DefaultXmlSerializationPolicy( + pedantic = false, + unknownChildHandler = IGNORING_UNKNOWN_CHILD_HANDLER + ) + repairNamespaces = true + } + @Test @Ignore fun generate() { - println(File(".").absolutePath) - val xmlFiles = - File("../scripts/devices").listFiles().toList().filter { it.name.endsWith(".xml") } - println(xmlFiles) - val fileContent = buildString { - appendLine( - """ - package com.github.takahirom.roborazzi + val deviceTypes = runBlocking { readAllDevices() }.groupBy { it.tagId } -object RobolectricDeviceQualifiers { - // Generated by [GenerateQualifiersTest.kt] - // Data from: AOSP https://android.googlesource.com/platform/tools/base/+/mirror-goog-studio-master-dev/sdklib/src/main/java/com/android/sdklib/devices - """.trimIndent() - ) - xmlFiles - .forEach { xmlFile -> - // parse xml - val documentBuilderFactory = DocumentBuilderFactory.newInstance() - val documentBuilder = documentBuilderFactory.newDocumentBuilder() - val document: Document = documentBuilder.parse(xmlFile) + FileSystem.SYSTEM.write("../roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt".toPath()) { - val devices = document.getElementsByTagName("d:device").toList() + writeUtf8( + """ + package com.github.takahirom.roborazzi - appendLine("// FROM: ${xmlFile.name}") - devices.forEach device@{ device -> + object RobolectricDeviceQualifiers { + // Generated by [GenerateQualifiersTest.kt] + // Data from: AOSP $devicesTarGz + + """.trimIndent() + ) + deviceTypes.forEach { (tagId, devices) -> + writeUtf8("\n\t// Type: ${tagId ?: "default"}\n") + devices.forEach { device -> // find device name - val deviceNodes = device.childNodes.toList() - val name = deviceNodes.first { it.nodeName == "d:name" }.textContent + val name = device.name .replace(" ", "") .replace("(", "") .replace(")", "") @@ -52,38 +68,24 @@ object RobolectricDeviceQualifiers { .replace(".", "") .replace("\"", "") - if (name[0] in '0'..'9') return@device println("skip device:$name") - val skippingDevicePrefixes = - listOf( - "GalaxyNexus", - "Pixel2", - "Pixel3", - "NexusS", - "Nexus10", - "Nexus4", - "Nexus5", - "Nexus6" - ) - if (skippingDevicePrefixes.any { name.startsWith(it) }) { - return@device println("skip device:$name") - } - val skippingDevices = listOf("Nexus72012", "Pixel") - if (skippingDevices.any { name == it }) { - return@device println("skip device:$name") + val shouldSkipDevice = shouldSkip(name, device) + + if (shouldSkipDevice) { + println("skip device:$name") + return@forEach } - val hardwareNodes = - deviceNodes.first { it.nodeName == "d:hardware" }.childNodes.toList() + val hardware = device.hardware - val screenNodes = hardwareNodes.first { it.nodeName == "d:screen" }.childNodes.toList() - val screenSize = screenNodes.first { it.nodeName == "d:screen-size" }.textContent - val screenRatio = screenNodes.first { it.nodeName == "d:screen-ratio" }.textContent - val pixelDensity = screenNodes.first { it.nodeName == "d:pixel-density" }.textContent - val dimensions = screenNodes.first { it.nodeName == "d:dimensions" }.childNodes.toList() - val xDimension = dimensions.first { it.nodeName == "d:x-dimension" }.textContent.toInt() - val yDimension = dimensions.first { it.nodeName == "d:y-dimension" }.textContent.toInt() + val screen = hardware.screen + val screenSize = screen.screenSize + val screenRatio = screen.screenRatio + val pixelDensity = screen.pixelDensity + val dimensions = screen.dimensions + val xDimension = dimensions.xDimension + val yDimension = dimensions.yDimension - val nav = hardwareNodes.first { it.nodeName == "d:nav" }.textContent + val nav = hardware.nav val densityValue = when (pixelDensity) { "ldpi" -> 120 "mdpi" -> 160 @@ -102,16 +104,16 @@ object RobolectricDeviceQualifiers { val screenRatioQualifier = if (screenRatio == "long") "long" else "notlong" val shapeQualifier = if (xDimension == yDimension) "round" else "notround" - val device = when (xmlFile.name) { - "wear.xml" -> { + val device = when (tagId) { + "android-wear" -> { "watch" } - "tv.xml" -> { + "android-tv" -> { "television" } - "automotive.xml" -> { + "android-automotive-playstore" -> { "car" } @@ -119,15 +121,58 @@ object RobolectricDeviceQualifiers { "any" } } - appendLine("const val ${name} = \"w${widthDp}dp-h${heightDp}dp-$screenSize-$screenRatioQualifier-$shapeQualifier-$device-$pixelDensity-keyshidden-$nav\"") + writeUtf8("\tconst val ${name} = \"w${widthDp}dp-h${heightDp}dp-$screenSize-$screenRatioQualifier-$shapeQualifier-$device-$pixelDensity-keyshidden-$nav\"\n") } } - appendLine("}") + + writeUtf8("\n}") } - File("../roborazzi/src/main/java/com/github/takahirom/roborazzi/RobolectricDeviceQualifiers.kt").writeText( - fileContent - ) + } + + private fun shouldSkip(name: String, device: Device): Boolean { + if (name[0] in '0'..'9') return true + val skippingDevicePrefixes = + listOf( + "GalaxyNexus", + "Pixel2", + "Pixel3", + "NexusS", + "Nexus10", + "Nexus4", + "Nexus5", + "Nexus6" + ) + if (skippingDevicePrefixes.any { name.startsWith(it) }) { + return true + } + val skippingDevices = listOf("Nexus72012", "Pixel") + if (skippingDevices.any { name == it }) { + return true + } + return false + } + + private suspend fun readAllDevices(): List { + val response = client.newCall(Request(devicesTarGz)).executeAsync() + + val archive = TarArchiveInputStream(GzipCompressorInputStream(response.body.byteStream())) + val devices = buildList { + var entry: TarArchiveEntry? = archive.nextTarEntry + while (entry != null) { + val content = archive.readByteString(entry.realSize.toInt()).utf8() + val withSingleNamespace = content.replace( + "http://schemas.android.com/sdk/devices/\\d+".toRegex(), + "http://schemas.android.com/sdk/devices/1" + ) + + val devices: Devices = xml.decodeFromString(withSingleNamespace) + addAll(devices.devices) + + entry = archive.nextTarEntry + } + } + return devices } private fun NodeList.toList(): List { diff --git a/scripts/download_device_xml.sh b/scripts/download_device_xml.sh deleted file mode 100644 index b3dd43c62..000000000 --- a/scripts/download_device_xml.sh +++ /dev/null @@ -1,4 +0,0 @@ -curl "https://android.googlesource.com/platform/tools/base/+archive/mirror-goog-studio-master-dev/sdklib/src/main/java/com/android/sdklib/devices.tar.gz" > devices.tar.gz -mkdir devices -tar -xzf devices.tar.gz -C devices -rm devices.tar.gz