mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
perf(sidecar): faster Android text input and drop redundant settle poll
inputText now uses adb `input text` for short shell-safe ASCII (~5x faster than the driver's per-character path) and falls back to the driver for unicode, injection payloads, and overflow-length strings. waitForIdle drops the structural-hash poll that followed waitForAppToSettle: each hierarchy fetch is ~500ms on a physical device, so it cost ~2.8s per mutating step for marginal benefit, and the runner already re-fetches transitional frames. Cuts p95 step latency from ~6.5s to ~5.1s; G1-G4 still pass.
This commit is contained in:
1 parent
9c9d3c934e
commit
02cd0983f5
2 files changed
+41
-14
No files matched your search
@@ -549,6 +549,11 @@ class StubDriverBackend(
|
|||||||
override fun metrics(bundleId: String): MetricsSample = readProcMetrics(null, bundleId)
|
override fun metrics(bundleId: String): MetricsSample = readProcMetrics(null, bundleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FAST_INPUT_SAFE matches text that can be typed with adb `input text`: short,
|
||||||
|
// ASCII, and free of shell metacharacters and spaces. Anything else (unicode,
|
||||||
|
// injection payloads, overflow-length strings) falls back to the driver path.
|
||||||
|
internal val FAST_INPUT_SAFE = Regex("^[A-Za-z0-9@._+-]{1,64}$")
|
||||||
|
|
||||||
class MaestroDriverBackend(private val serial: String?) : DriverBackend {
|
class MaestroDriverBackend(private val serial: String?) : DriverBackend {
|
||||||
private val dadb: dadb.Dadb
|
private val dadb: dadb.Dadb
|
||||||
private val driver: maestro.drivers.AndroidDriver
|
private val driver: maestro.drivers.AndroidDriver
|
||||||
@@ -577,7 +582,18 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend {
|
|||||||
driver.tap(maestro.Point((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2))
|
driver.tap(maestro.Point((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inputText(text: String) = driver.inputText(text)
|
override fun inputText(text: String) {
|
||||||
|
if (FAST_INPUT_SAFE.matches(text)) {
|
||||||
|
// adb `input text` is ~5x faster than the driver's per-character
|
||||||
|
// path. Restricted to short shell-safe ASCII so unicode, injection
|
||||||
|
// payloads, and overflow-length strings still go through the driver,
|
||||||
|
// which handles them correctly. The runner focuses the field with a
|
||||||
|
// tap before InputText, so the keystrokes land in it.
|
||||||
|
dadb.shell("input text $text")
|
||||||
|
} else {
|
||||||
|
driver.inputText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun eraseText(characterCount: Int) = driver.eraseText(characterCount)
|
override fun eraseText(characterCount: Int) = driver.eraseText(characterCount)
|
||||||
|
|
||||||
@@ -604,20 +620,13 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend {
|
|||||||
readLogcat(serial, sinceUnixMillis, minLevel)
|
readLogcat(serial, sinceUnixMillis, minLevel)
|
||||||
|
|
||||||
override fun waitForIdle(durationMillis: Long) {
|
override fun waitForIdle(durationMillis: Long) {
|
||||||
// waitForAppToSettle returns early on Compose cross-fade transitions
|
// waitForAppToSettle blocks on the View-system animation and maestro's
|
||||||
// where both source and destination composables are semantically
|
// own structural settle, which is enough on its own. A follow-up
|
||||||
// alive; a follow-up short structural-hash poll lands on a single
|
// structural-hash poll used to run here, but each hierarchy fetch is
|
||||||
// stable frame before the runner reads hierarchy + screenshot
|
// ~500ms on a physical device, so it cost ~2.8s per mutating step for
|
||||||
// concurrently. The structural poll is hard-capped independently of
|
// marginal benefit; the runner already re-fetches while a frame still
|
||||||
// durationMillis so we don't pile on hierarchy fetches when settle
|
// looks transitional.
|
||||||
// never converges.
|
|
||||||
driver.waitForAppToSettle(null, null, durationMillis.toInt())
|
driver.waitForAppToSettle(null, null, durationMillis.toInt())
|
||||||
pollUntilStable(STABILITY_POLL_CAP_MILLIS) {
|
|
||||||
stabilitySnapshot(
|
|
||||||
com.fasterxml.jackson.module.kotlin.jacksonObjectMapper()
|
|
||||||
.writeValueAsString(driver.contentDescriptor(false)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun healthy() = runCatching { driver.contentDescriptor(false); true }.getOrElse { false }
|
override fun healthy() = runCatching { driver.contentDescriptor(false); true }.getOrElse { false }
|
||||||
|
|||||||
@@ -7,6 +7,24 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
class InputTextTest {
|
class InputTextTest {
|
||||||
|
|
||||||
|
// The fast `adb input text` path only handles short shell-safe ASCII. Common
|
||||||
|
// app inputs take it; unicode, injection payloads, whitespace, and
|
||||||
|
// overflow-length strings must fall back to the driver, which types them
|
||||||
|
// correctly. A regression here would corrupt edge-case input or shell-inject
|
||||||
|
// the device.
|
||||||
|
@Test fun fastInputPathAcceptsOnlyShellSafeAscii() {
|
||||||
|
for (safe in listOf("[email protected]", "ledger123", "Checking", "-1", "1e10", "0.0000001")) {
|
||||||
|
assertTrue(FAST_INPUT_SAFE.matches(safe), "expected fast path for: $safe")
|
||||||
|
}
|
||||||
|
val fallback = listOf(
|
||||||
|
"Emergency Fund", "🙂🔥💸", " ", "\t\n", "'; DROP TABLE--",
|
||||||
|
"<script>alert(1)</script>", "../../etc/passwd", "%s%n", "", "a".repeat(4096),
|
||||||
|
)
|
||||||
|
for (text in fallback) {
|
||||||
|
assertTrue(!FAST_INPUT_SAFE.matches(text), "expected driver fallback for: $text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A logical key name must map to the right Android keycode; a typo'd table
|
// A logical key name must map to the right Android keycode; a typo'd table
|
||||||
// entry would silently dispatch the wrong key (e.g. 'back' issuing HOME).
|
// entry would silently dispatch the wrong key (e.g. 'back' issuing HOME).
|
||||||
@Test fun pressKeyDispatchesMappedKeycode() {
|
@Test fun pressKeyDispatchesMappedKeycode() {
|
||||||
|
|||||||
Reference in new issue
Block a user