From da6612890faf3df0f3784a7e43ce313281f21679 Mon Sep 17 00:00:00 2001 From: PJ Date: Tue, 9 Jun 2026 19:52:10 +0530 Subject: [PATCH] feat(sidecar): reach USB devices via the adb server by serial --- .../dev/sanderling/sidecar/DriverBackend.kt | 24 ++++++++++++++----- .../dev/sanderling/sidecar/DadbTargetTest.kt | 19 +++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 sidecar/src/test/kotlin/dev/sanderling/sidecar/DadbTargetTest.kt diff --git a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt index a8dece6..e2f0946 100644 --- a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt +++ b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt @@ -630,12 +630,24 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend { } } -private fun buildDadb(serial: String?): dadb.Dadb { - return if (serial == null) { - dadb.Dadb.create("localhost", 5555) - } else { - dadb.Dadb.create(serial.substringBefore(":"), serial.substringAfter(":").toIntOrNull() ?: 5555) - } +internal sealed interface DadbTarget { + data class Tcp(val host: String, val port: Int) : DadbTarget + data class Server(val serial: String) : DadbTarget +} + +// A host:port serial connects to adbd directly; any other serial is reached +// through the adb server, the only path to a USB-attached device. A null serial +// keeps the emulator loopback default. +internal fun dadbTargetFor(serial: String?): DadbTarget { + if (serial == null) return DadbTarget.Tcp("localhost", 5555) + val colon = serial.lastIndexOf(':') + val port = if (colon >= 0) serial.substring(colon + 1).toIntOrNull() else null + return if (port != null) DadbTarget.Tcp(serial.substring(0, colon), port) else DadbTarget.Server(serial) +} + +private fun buildDadb(serial: String?): dadb.Dadb = when (val target = dadbTargetFor(serial)) { + is DadbTarget.Tcp -> dadb.Dadb.create(target.host, target.port) + is DadbTarget.Server -> dadb.adbserver.AdbServer.createDadb("localhost", 5037, "host:transport:${target.serial}") } internal fun findBoundsBySelector(root: maestro.TreeNode, selector: String): IntArray? { diff --git a/sidecar/src/test/kotlin/dev/sanderling/sidecar/DadbTargetTest.kt b/sidecar/src/test/kotlin/dev/sanderling/sidecar/DadbTargetTest.kt new file mode 100644 index 0000000..ae75b2c --- /dev/null +++ b/sidecar/src/test/kotlin/dev/sanderling/sidecar/DadbTargetTest.kt @@ -0,0 +1,19 @@ +package dev.sanderling.sidecar + +import org.junit.Test +import kotlin.test.assertEquals + +class DadbTargetTest { + + @Test fun nullSerialDefaultsToEmulatorLoopback() { + assertEquals(DadbTarget.Tcp("localhost", 5555), dadbTargetFor(null)) + } + + @Test fun hostPortSerialConnectsDirectly() { + assertEquals(DadbTarget.Tcp("192.168.1.243", 5555), dadbTargetFor("192.168.1.243:5555")) + } + + @Test fun usbSerialRoutesThroughAdbServer() { + assertEquals(DadbTarget.Server("663c91b1"), dadbTargetFor("663c91b1")) + } +}