mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(sidecar): DriverService implementing v0.1 RPCs
DriverService delegates each RPC to a pluggable DriverBackend so real Maestro integration can land in a follow-up without changing the gRPC layer. StubDriverBackend satisfies the contract and lets the Go side talk to a running sidecar today.
This commit is contained in:
1 parent
d9800b648f
commit
125a060a69
2 files changed
+156
No files matched your search
@@ -0,0 +1,56 @@
|
||||
package dev.uatu.sidecar
|
||||
|
||||
interface DriverBackend {
|
||||
fun launch(bundleId: String, clearState: Boolean)
|
||||
fun terminate(bundleId: String)
|
||||
fun tap(x: Int, y: Int)
|
||||
fun inputText(text: String)
|
||||
fun screenshot(): Triple<ByteArray, Int, Int>
|
||||
fun hierarchy(): String
|
||||
fun waitForIdle(durationMillis: Long)
|
||||
fun healthy(): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* StubDriverBackend records calls but takes no real device action. Real
|
||||
* Maestro integration arrives in a follow-up; v0.1 wires the gRPC plumbing
|
||||
* end-to-end so the Go side can be exercised against a running sidecar
|
||||
* even before Maestro is plugged in.
|
||||
*/
|
||||
class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
@Volatile var launchCount: Int = 0
|
||||
private set
|
||||
@Volatile var lastBundleId: String? = null
|
||||
private set
|
||||
@Volatile var lastTap: Pair<Int, Int>? = null
|
||||
private set
|
||||
@Volatile var lastInputText: String? = null
|
||||
private set
|
||||
|
||||
override fun launch(bundleId: String, clearState: Boolean) {
|
||||
launchCount++
|
||||
lastBundleId = bundleId
|
||||
}
|
||||
|
||||
override fun terminate(bundleId: String) {
|
||||
lastBundleId = null
|
||||
}
|
||||
|
||||
override fun tap(x: Int, y: Int) {
|
||||
lastTap = x to y
|
||||
}
|
||||
|
||||
override fun inputText(text: String) {
|
||||
lastInputText = text
|
||||
}
|
||||
|
||||
override fun screenshot(): Triple<ByteArray, Int, Int> = Triple(ByteArray(0), 0, 0)
|
||||
|
||||
override fun hierarchy(): String = "{\"children\":[],\"platform\":\"$platform\"}"
|
||||
|
||||
override fun waitForIdle(durationMillis: Long) {
|
||||
if (durationMillis > 0) Thread.sleep(durationMillis)
|
||||
}
|
||||
|
||||
override fun healthy(): Boolean = true
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package dev.uatu.sidecar
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import dev.uatu.driver.v1.DriverGrpc
|
||||
import dev.uatu.driver.v1.Duration
|
||||
import dev.uatu.driver.v1.Empty
|
||||
import dev.uatu.driver.v1.HealthStatus
|
||||
import dev.uatu.driver.v1.HierarchyJSON
|
||||
import dev.uatu.driver.v1.Image
|
||||
import dev.uatu.driver.v1.LaunchRequest
|
||||
import dev.uatu.driver.v1.Point
|
||||
import dev.uatu.driver.v1.Text
|
||||
import io.grpc.stub.StreamObserver
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class DriverService(
|
||||
private val platform: String = "android",
|
||||
private val serial: String? = null,
|
||||
private val backend: DriverBackend = StubDriverBackend(platform),
|
||||
) : DriverGrpc.DriverImplBase() {
|
||||
|
||||
private val launchedBundleId = AtomicReference<String?>(null)
|
||||
|
||||
override fun launch(request: LaunchRequest, responseObserver: StreamObserver<Empty>) {
|
||||
runRpc(responseObserver) {
|
||||
backend.launch(request.bundleId, request.clearState)
|
||||
launchedBundleId.set(request.bundleId)
|
||||
Empty.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun terminate(request: Empty, responseObserver: StreamObserver<Empty>) {
|
||||
runRpc(responseObserver) {
|
||||
launchedBundleId.get()?.let { backend.terminate(it) }
|
||||
launchedBundleId.set(null)
|
||||
Empty.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun tap(request: Point, responseObserver: StreamObserver<Empty>) {
|
||||
runRpc(responseObserver) {
|
||||
backend.tap(request.x, request.y)
|
||||
Empty.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun inputText(request: Text, responseObserver: StreamObserver<Empty>) {
|
||||
runRpc(responseObserver) {
|
||||
backend.inputText(request.value)
|
||||
Empty.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun screenshot(request: Empty, responseObserver: StreamObserver<Image>) {
|
||||
runRpc(responseObserver) {
|
||||
val (png, width, height) = backend.screenshot()
|
||||
Image.newBuilder()
|
||||
.setPng(ByteString.copyFrom(png))
|
||||
.setWidth(width)
|
||||
.setHeight(height)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hierarchy(request: Empty, responseObserver: StreamObserver<HierarchyJSON>) {
|
||||
runRpc(responseObserver) {
|
||||
HierarchyJSON.newBuilder().setJson(backend.hierarchy()).build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun waitForIdle(request: Duration, responseObserver: StreamObserver<Empty>) {
|
||||
runRpc(responseObserver) {
|
||||
backend.waitForIdle(request.millis)
|
||||
Empty.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun health(request: Empty, responseObserver: StreamObserver<HealthStatus>) {
|
||||
runRpc(responseObserver) {
|
||||
HealthStatus.newBuilder()
|
||||
.setReady(backend.healthy())
|
||||
.setVersion(VERSION)
|
||||
.setPlatform(platform)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> runRpc(observer: StreamObserver<T>, block: () -> T) {
|
||||
try {
|
||||
observer.onNext(block())
|
||||
observer.onCompleted()
|
||||
} catch (cause: Exception) {
|
||||
observer.onError(cause)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val VERSION: String = "0.0.1"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user