Skip to content

Commit

Permalink
Update device definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
yschimke committed May 1, 2023
1 parent f6b3934 commit 21f9bca
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 94 deletions.
6 changes: 6 additions & 0 deletions roborazzi/build.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"

}
56 changes: 56 additions & 0 deletions roborazzi/src/test/java/com/github/takahirom/roborazzi/Devices.kt
Original file line number Diff line number Diff line change
@@ -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<Device>)

@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
)
Original file line number Diff line number Diff line change
@@ -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(")", "")
Expand All @@ -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
Expand All @@ -102,32 +104,75 @@ 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"
}

else -> {
"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<Device> {
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<Node> {
Expand Down
4 changes: 0 additions & 4 deletions scripts/download_device_xml.sh

This file was deleted.

0 comments on commit 21f9bca

Please sign in to comment.