Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Android physical devices through the host's default adb server

Aperta
#441 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
18/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
android, typescript
Ambito
mobile

Direzione di ricerca

Start with src/drivers/android/index.ts to see how AndroidDriver routes calls, then adb-server.ts for the existing probe and timeout pattern the spec reuses. The two new files are default-adb-server.ts (protocol gate) and physical-handler.ts; the spec names their functions and the reason codes. Done means each row of the input table gives the stated result against the fake driver, and a blocked protocol runs no adb command.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

task:draft

Part of #428.

Scope

After this PR, an operator can enroll a real Android phone, and agents can lease it, through the Android driver. The driver reaches phones through the host's default adb server (port 5037), always with -s <serial>. It inspects a phone for device add, reads which enrolled phones are present, reclaims a released phone by uninstalling user apps, and runs adb for device.exec on a leased phone. (The lease environment, ANDROID_SERIAL and ANDROID_ADB_SERVER_PORT=5037, is #438's.) Right before every adb command it sends for a phone, device.exec's included, it reads the default server's adb protocol version afresh. It sends nothing when that differs from Simlock's adb or cannot be read. doctor then names both versions. Tasks 1a–3 built the core side against the fake driver; this task is the real Android side of ADR 0022 §5–§8. The phone lane gets its Android test.

In short

Today, after tasks 1a–4, device add --platform android cannot enroll anything: the Android driver has no physical handler. After this PR, a phone plugged in by USB with USB debugging authorized can be enrolled, leased and reclaimed. Simlock never stops the host's adb server. Right before each Android physical command, Simlock checks that server's adb protocol version, and sends the command only when the server speaks Simlock's version or nothing listens on its port.

New terms:

  • default adb server: the adb server on port 5037 that plain adb and Android Studio use, e.g. the one adb devices talks to on a Mac. Simlock's own server (port 5038, USB off) stays for emulators only.
  • USB transport: a line of adb devices -l that carries a usb: field, e.g. R58M123ABC device usb:1-1 product:o1s model:SM_G991B transport_id:3. A line without it (192.168.1.5:5555 device …) is a network transport. The transport decides; the serial's shape never does.
  • adb protocol version: the number an adb client and server must share, or the client kills the server and starts its own. adb version prints it last (Android Debug Bridge version 1.0.41 is 41). The server answers it to host:version over its socket, which restarts nothing.
  • physical handler: the part of the Android driver that serves physical devices. The driver routes each call to it when the device is physical: true.
flowchart LR
  A["device add / grant / watcher / release / device.exec"] --> D["AndroidDriver"]
  D -->|"physical: false"| E["emulator side<br/>Simlock's server, unchanged"]
  D -->|"physical: true"| P["physical handler (new)"]
  P --> V{"host:version on 5037, before each command<br/>same protocol? (new)"}
  V -->|yes| C["adb -P 5037 -s &lt;serial&gt; …"]
  V -->|"no / unreadable"| N["no command: not present, refused;<br/>doctor names both versions"]
# before (tasks 1a-4 landed)
$ simlock device add --platform android R58M123ABC
Refused: this driver cannot read physical devices yet.   (#437's placeholder, reason not-connected)

# after
$ simlock device add --platform android R58M123ABC
Enrolled SM-G991B (API 34), 12 user apps recorded.

$ simlock doctor        # Android Studio's older adb runs the default server
driver-advisory android adb-protocol-mismatch: the adb server on port 5037 speaks protocol 40 and Simlock's adb speaks 41. …
Input Before (after task 4) After
device add --platform android R58M123ABC, phone on USB, USB debugging authorized refused enrolled under R58M123ABC: ro.product.model, API level, user apps of the current user
same, USB debugging not authorized (unauthorized) refused refused: USB debugging not authorized (untrusted)
device add --platform android 192.168.1.5:5555 (network transport only) refused refused: only USB-connected devices can be enrolled (network-only)
device add --platform android emulator-5554 (a developer's own emulator on the default server) refused refused: only USB-connected devices can be enrolled (network-only)
device add --platform android R58M123ABC, not listed, or listed offline refused refused: not connected (not-connected)
any device add --platform android … while the default server speaks another protocol refused refused: not connected (not-connected), the message naming both protocol versions; no adb command runs
device add --platform android R58M123ABC while a read fails or times out (host:version, adb devices -l, getprop, user id, package list) refused refused: could not read the device, naming the read (unreadable)
lease --platform android --physical, the enrolled phone free and present no phone enrollable granted; environment ANDROID_SERIAL=R58M123ABC, ANDROID_ADB_SERVER_PORT=5037 (#438)
an enrolled phone unauthorized, or unplugged, for the grace period stays ready (no real presence read) absent (untrusted), absent (not-present)
an enrolled phone with its screen locked — present: adb shows no lock state, so it stays in rotation
release of that lease — each user app not on the enrollment list uninstalled for the current user; listed apps found missing reported
device.exec adb shell getprop on that lease — runs adb -P 5037 -s R58M123ABC shell getprop
device.exec adb kill-server (or start-server, connect, server, detach, … as the subcommand) on that lease — refused
device.exec adb reboot bootloader (or sideload, sideload-auto-reboot, recovery, fastboot after reboot) on that lease — refused: it takes the phone off adb
device.exec adb shell getprop on that lease while the default server speaks another protocol — refused, naming both protocol versions; nothing runs
device.exec adb shell dumpsys usb on that lease — runs: a refused word after the subcommand is an operand
the default server switches to another protocol while the phone is leased — no command sent; the phone reads not present, so the lease ends device-lost after the grace period (task 3)
simlock adb devices, lease --platform android Simlock's server, emulators unchanged

If this goes wrong, an operator would see an authorized phone refused, apps left on a released phone, Android Studio's adb server restarted, emulator leases stalling while a phone hangs, or a command run on a phone nobody enrolled.

Technical spec

Modules touched
flowchart LR
  CORE["core callers: enrollment, grant, reclaim,<br/>watcher, device.exec (tasks 1b-4)"] --> AD["drivers/android/index.ts<br/>AndroidDriver *"]
  DOC["core/doctor: advisories"] --> AD
  DSP["daemon/dispatcher *<br/>(unsetEnv)"] --> AD
  AD --> EM["emulator side: AdbServerSupervisor,<br/>AdbRegistrar (unchanged)"]
  AD --> PH["drivers/android/physical-handler.ts (new)"]
  PH --> PG["drivers/android/default-adb-server.ts (new)<br/>protocol gate"]
  PH --> PR["ports: ProcessRunner"]
  PG --> TCP["ports: TcpProbe, Filesystem"]
  PG --> PR
  EM --> PR
  EM --> TCP

One device add through the hops, with the failure reply:

sequenceDiagram
  participant C as CLI
  participant D as Daemon (enrollment, task 1b)
  participant P as Android physical handler
  participant S as Default adb server :5037
  participant R as Phone on USB
  C->>D: device add --platform android R58M123ABC
  D->>P: inspectPhysical("R58M123ABC")
  P->>S: host:version (socket)
  S-->>P: OKAY 0004 0029 (protocol 41)
  alt same protocol as Simlock's adb
    Note over P,S: a fresh host:version right before each adb command below
    P->>S: adb -P 5037 devices -l
    S-->>P: R58M123ABC device usb:1-1 …
    P->>R: -s R58M123ABC: getprop, am get-current-user, pm list packages -3
    R-->>P: model, API level, user id, apps
    P-->>D: inspection
    D-->>C: Enrolled SM-G991B (API 34), 12 user apps recorded
  else other protocol (at any check), no USB transport in state device, or a failed read
    P-->>D: refusal with its reason (unreadable for a failed read); no command named the serial before its line was read
    D-->>C: Refused: <reason>
  end
  • src/drivers/android/default-adb-server.ts (new): the protocol check ("the gate"), ensureAdbProtocol(). It is the one place that decides whether a physical command may run (architecture rule 10).
    • Simlock's protocol, read first: the last number of adb version's first line (1.0.41 → 41). Read with Simlock's adb, 30 s limit; adb version talks to no server. Cached with the adb binary's modifiedAtMs and size (Filesystem.stat, src/ports/filesystem.ts:167). Each check stats the binary and reads the version again when either changed, so a platform-tools update under a running daemon is seen. A failed read is not cached.
    • The server's protocol: TcpProbe.send(5037, "000chost:version", 5000), a 5 s limit (the IDENTITY_TIMEOUT_MS precedent in adb-server.ts:16). No isListening call: it answers false on a timeout too (src/ports/tcp-probe.ts:53-57), which would fail open. A reply OKAY + 0004 + four hex digits is the version.
    • Answer (safety rule 9 as amended, fail closed):
      • blocked when Simlock's own version cannot be read, whatever the server answers;
      • open when send rejects with ECONNREFUSED: nothing listens, and Simlock's adb starts a server of its own version, as any adb does;
      • open when both numbers match;
      • blocked when they differ, when send rejects with any other error, when it times out, and when the reply is anything else (an empty reply included).
    • Every physical adb command awaits its own check right before it is sent: each adb devices -l, each getprop, each user id read, each pm list packages, each pm uninstall, and the command passthrough returns. advisories() runs one check too. Nothing keeps the server's answer: there is no stored verdict, no timer, and the driver needs no health.probeIntervalMs. A fixed server gets the next command, with no restart.
    • A blocked check sends nothing for that command. What the calling method then answers is below, method by method.
    • Between a check and its command (milliseconds), another client can start a server of another protocol. Simlock's adb client then restarts it, as any adb client does. The KNOWN-PITFALLS entry below names this.
    • Never sends kill, never runs kill-server or start-server, never signals a process. It sends nothing to 5037 but host:version.
  • src/drivers/android/physical-handler.ts (new): every physical call. Each runs adb -P 5037, with the injected base environment plus ANDROID_ADB_SERVER_PORT=5037, and without ANDROID_ADB_SERVER_ADDRESS, ADB_SERVER_SOCKET or ANDROID_SERIAL from that base (M6): each of them could aim adb at another server or device. It never uses AndroidDriver#env (index.ts:713), which points at Simlock's server. Limits: 30 s per read, 60 s per uninstall (ADR 0022 §5; architecture rule 11). A read past its limit, or with no exit code, is a failed read. Physical calls take no per-device lock and no port allocation, so they never wait behind, or block, an emulator call.
    • inspectPhysical(id): one adb devices -l, after its check. The line whose serial equals id decides:
      • USB transport in state device: inspected. Canonical ID is that serial. Then, with -s <serial>: shell getprop ro.product.model (model), shell getprop ro.build.version.sdk (OS version, the API level), shell am get-current-user (user id), shell pm list packages -3 --user <id> (enrollment app list). Class is always phone (ADR 0022 §6: every Android device is phone).
      • The user id, here and in reclaim, is a claim (safety rule 10): after trimming, it must be 1 to 6 digits, or the read fails. So no unchecked text reaches adb shell.
      • USB transport in state unauthorized: refused untrusted, USB debugging not authorized.
      • Only network transports, or an emulator line (emulator-5554 device …, no usb: field): refused network-only, "only USB-connected devices can be enrolled".
      • Not listed, or another state (offline, no permissions, …): refused not-connected.
      • A check blocked because the versions differ, before any of these commands: refused not-connected, with a message naming both protocol versions. No adb command is sent after that check.
      • A check blocked because a version could not be read, before any of these commands: refused unreadable, with a message naming the read: "Simlock's adb version" when adb version gave no protocol number, "the adb server's version on port 5037" when the server's answer is not a version, the check timed out, or send rejected with anything but ECONNREFUSED. These are the cases where advisories() reports (below). No adb command is sent after that check.
      • A failed or timed-out adb devices -l, or a failed read after the device was found (getprop, user id, package list): refused unreadable, with a message naming the read. Nothing is written. inspectPhysical never rejects for a failed read.
      • The reason codes are #437's DEVICE_NOT_ENROLLABLE reasons.
      • No command names the serial until its line is a USB transport in state device.
    • listPresentPhysical(enrolledIds): one adb devices -l, after its check. Its callers in the core: #438's grant, and #439's watcher, presence read after a failed reclaim, and paused quarantine retry. One entry per given serial, none for any other (safety rule 8 as amended): a serial that is not enrolled gets no entry and no command. Present: on a USB transport in state device, with its OS version from getprop ro.build.version.sdk on that serial (#439's osVersion). Not present untrusted: state unauthorized. Not present not-present: missing, offline, another state, or only on a network transport. A locked phone is present: adb shows no lock state (ADR 0022 §7). A failed or timed-out listing, a failed getprop on a serial, or a blocked check answers the serials concerned not present not-present and does not reject (ADR 0022 §5: a read past its limit is "not present"). A check blocked before adb devices -l concerns every given serial; one before a serial's getprop concerns that serial.
    • reclaim of a physical device: read the user id and the user's pm list packages -3; for each package not on enrolledApps, shell pm uninstall --user <id> <package>, one at a time, stopping at the first failure; then list again. Each of these commands waits for its own check. The packages come from #438's uninstallPlan. Result: strategy uninstall, missingApps = enrolled packages not on the first list. When it rejects, the core reads presence and picks absent or quarantined (task 3). It rejects, and sends no later command:
      • when a check is blocked;
      • when the user id read fails, passes 30 s, or prints a bad user id (above): no pm list packages and no uninstall runs;
      • when the first list fails, passes 30 s, or has a bad line (below): no uninstall runs. It never resolves with an empty list;
      • when an uninstall exits non-zero, prints anything but Success, or passes 60 s;
      • when the second list fails, passes 30 s, or has a bad line, or still holds a package off the enrollment list.
    • Package lines are a claim (safety rule 10). A trailing \r is stripped, and blank lines are ignored. Every other line must be package: followed by one or more letters, digits, _ and . only. Any other line, package: with no name included, fails the read, so no unchecked text reaches adb shell.
    • passthrough("adb", <physical device>, args, context) (the signature task 4 left, ADR 0022 §5; #440 made it return a promise): it awaits its own check, then returns adb -P 5037 -s <serial> <args>, env ANDROID_ADB_SERVER_PORT=5037, and unsetEnv ANDROID_ADB_SERVER_ADDRESS, ADB_SERVER_SOCKET, ANDROID_SERIAL (below). The dispatcher spawns it as soon as the promise resolves. Refused with PassthroughRefusedError, and nothing is spawned:
      • when its check is blocked. The message names both protocol versions, or the version it could not read, as advisories() does;
      • any caller argument before the subcommand (-s, -t, -d, -e, -P, -H, -L, --one-device, any other): Simlock supplies the scope, so the command stays aimed at the leased phone. So args[0] is the subcommand;
      • args[0] is kill-server, start-server, server, nodaemon, connect, disconnect, pair, reconnect, tcpip, usb or detach: each stops, starts, restarts or moves a server or transport other holders share, or takes the phone off USB for the next holder. The same word later in the line is an operand and runs (shell dumpsys usb);
      • args[0] is reboot-bootloader (adb's older spelling of reboot bootloader), or args[0] is reboot and args[1] is bootloader, sideload, sideload-auto-reboot, recovery or fastboot: each takes the phone off adb. Any other reboot runs;
      • bare shell with no terminal, as for emulators.
    • estimate({operation: "reclaim"}, <physical spec>): 90 000 ms at both clean levels: one 30 s read limit plus one 60 s uninstall limit. Two callers read it: every ready grant's estimatedReclaimMs (src/leasing/lease-acquisition-coordinator.ts:1128), and doctor's stall rule for an unclaimed reclaim (stallThresholdMs, core/doctor.ts:973, via :908-909).
  • src/core/driver.ts PassthroughCommand (:439): an optional unsetEnv: readonly string[]. src/daemon/dispatcher.ts #deviceExec (:539, the env at :559) awaits passthrough as #440 left it, builds the env as today, { ...execEnv, ...command.env }, then deletes each unsetEnv key. Today execEnv is the daemon's process.env (src/daemon/main.ts:436), so without this a daemon started with ANDROID_ADB_SERVER_ADDRESS would aim an agent's command at another server. Only the physical Android command sets it.
  • src/drivers/android/index.ts: AndroidDriver routes on physical to the handler for inspectPhysical, listPresentPhysical, reclaim, passthrough and estimate. leaseEnvironment keeps #438's physical answer. estimate and leaseEnvironment send nothing to 5037 and start no process. Every emulator path is unchanged. advisories() (new on this driver) runs one check, whether or not any Android device is enrolled. It returns adb-protocol-mismatch when Simlock's own version cannot be read (whatever the server answers), when the server answers another version, when its answer is not a version, or when the check times out or send rejects with anything but ECONNREFUSED. It returns nothing on a match, and nothing on ECONNREFUSED: nothing listens. The message says which of these it is: it names both protocol numbers, or "unreadable" for the one it could not read. It says no command goes to physical Android devices until they match, and that Simlock will not restart that server. dispose() is unchanged: it stops Simlock's own server and nothing on 5037.
  • The phone lane for Android, with task 4's two tags (physical and physical-manual). Both files are skipped unless SIMLOCK_PHYSICAL_ANDROID names the phone's USB serial and SIMLOCK_PHYSICAL_ANDROID_APK names an APK to install. The skip message names the missing variable.
    • e2e/slow-android-physical.test.ts (new), tag physical: needs no person, runs unattended;
    • e2e/slow-android-physical-manual.test.ts (new), tag physical-manual: unplug and plug back. Each step is a prompt in the log, and the test waits up to 150 s for it. The hardware verifier never runs it: its toolchain row needs a person at the phone (below).
  • docs/internal/agent-rules/toolchain.md: in the slow-lane table task 4 extended, a row for the Android phone: command -v adb finds it, adb devices -l lists $SIMLOCK_PHYSICAL_ANDROID on a USB transport in state device, SIMLOCK_PHYSICAL_ANDROID_APK names an APK. The commands are scripts/slow-e2e.sh --physical e2e/slow-android-physical.test.ts and, by hand, scripts/slow-e2e.sh --physical-manual e2e/slow-android-physical-manual.test.ts. The manual file's row adds the machine check "a person at the phone", as #440's iOS row does: SIMLOCK_PHYSICAL_PERSON=1, set only by a person running it by hand. The hardware verifier never sets it, so it reports the manual file unavailable (needs a person), its existing outcome.
  • docs/CLI.md:
    • Prerequisites gain "Android physical devices": connected by USB, USB debugging authorized for this host. Simlock reaches them through the host's default adb server (port 5037), which it starts if none runs and never stops. Simlock cannot see an Android lock screen: a locked phone stays in rotation.
    • The doctor section gains the adb-protocol-mismatch advisory: what it means and that the fix is running that server with an adb of the same protocol.
  • docs/internal/KNOWN-PITFALLS.md: an entry with two parts.
    • A default server of another protocol, started between Simlock's check and the command right after it, is restarted by Simlock's adb client. adb offers no way to stop that. The window is milliseconds long, for Simlock's own calls and for device.exec alike.
    • The default server a physical adb command starts is not in that command's process group. adb's fork-server calls setsid: on the maintainer's Mac, adb -L tcp:5037 fork-server has parent pid 1 and its own process group. So when NodeProcessRunner kills a timed-out physical command by its group (killProcessTree, src/ports/process-runner.ts:508), the server keeps running.
  • docs/internal/agent-rules/safety.md rule 9 and ADR 0022 §5 and §12 already say the check runs right before every physical command (#443). This task makes the code match; it does not edit them.
Contract and event changes

None. PassthroughCommand.unsetEnv is an internal driver type, not a contract shape. Searched src/contract/ and src/bus/index.ts: the driver methods, PresentPhysical with osVersion and its reasons, ReclaimResult.missingApps, strategy uninstall, absentReason and the events were added by tasks 1b–3. The driver-advisory doctor finding exists (core/doctor.ts:88); its code is the driver's own text. No protocol bump.

Other code on the same state

The state: the default adb server on 5037, the enrolled phones on it, and Simlock's cached adb version. Line numbers are on main at ac4cf1e; tasks 1a–4 move them.

  • AdbServerSupervisor (src/drivers/android/adb-server.ts:104) owns Simlock's server. It refuses 5037 as its port (adb-server.ts:167), and starts its server with ADB_USB=0 (:43-49), so phones never appear there. The handler never uses Simlock's port. No overlap.
  • Every emulator call goes through AndroidDriver#env (index.ts:713): listManaged (:1015) and #scanAdbSerials (:1067, matches only emulator-\d+), makeReady, emulator reclaim (:932), passthrough with no device (:650), leaseEnvironment for an emulator (:636). None reaches 5037; the handler reaches nothing else. Unchanged.
  • #withDeviceLock (index.ts:2041) and PortAllocator (index.ts:2068) serialise emulator work. Physical calls take neither, so a hung phone call never delays an emulator call, and the reverse.
  • NodeProcessRunner (src/ports/process-runner.ts:177) spawns every command detached, and kills a timed-out one by its process group (killProcessTree, :508). A physical adb that finds nothing on 5037 forks the default server, which leaves the group (setsid). So the kill ends the client and never the server. The KNOWN-PITFALLS entry names this. The phone lane proves only that a timed-out exec leaves a running server alone; the forked case rests on the observed process group, since forcing it would stop the host's server.
  • AdbRegistrar (src/drivers/android/adb-registrar.ts:25) sends host:emulator to Simlock's port only. The gate sends host:version to 5037 only. Different servers.
  • Doctor#collectAdvisories (src/core/doctor.ts:492) calls advisories(); a rejecting call contributes nothing. So advisories() returns the mismatch as a finding and never rejects for it.
  • Core callers of the physical methods: enrollment (task 1b); grant presence check and reclaim (task 2); the physical-device watcher, the presence read after a failed reclaim, and the paused quarantine retry's presence read (task 3); estimate from grant timing and doctor (above); device.exec routing (src/core/driver-catalog.ts:74, src/daemon/dispatcher.ts:539, as task 4 left them). The core's operation claim orders enrollment, reclaim and quarantine retries; the driver keeps no per-device state. The watcher's presence read can run while a reclaim uninstalls. Both are calls on one adb server; the read changes nothing on the phone. The core's claim decides the state, so the reclaim's outcome wins.
  • Other adb clients on the host (Android Studio, an agent's plain adb, a second Simlock instance) share 5037 and see the phones. One of another protocol restarts the server, as adb does; Simlock's next check sees it, and sends nothing. Simlock never restarts it, except in the window the KNOWN-PITFALLS entry names. An agent's own adb on its leased phone is the agent's; Simlock uninstalls only after release.
  • A device.exec still running when the watcher marks the phone absent runs until it ends or exec.timeoutMs stops it, as for emulators today (KNOWN-PITFALLS "A device.exec command is authorized once, at its start").
Rules in play
  • safety.md rule 1 as amended: the only destructive act is pm uninstall of a user app off the enrollment list, for the current user. No reboot, erase, or command on a phone that is not enrolled, apart from the admin's own device add inspection.
  • safety.md rules 7, 8 as amended: presence reads only enrolled serials; a non-enrolled serial gets no entry and no command.
  • safety.md rule 9 as amended: the default server is never stopped by Simlock. Right before each physical command, Simlock checks that server's adb protocol version, and sends nothing when it differs or the server does not answer (#443's wording). The check fails closed: only a refused connection means nothing listens, and then adb starts a server. This task does not edit the rule.
  • safety.md rule 10: adb devices -l lines, the user id and package lines are claims; the user id and package names are checked before they reach adb shell.
  • architecture.md rules 2, 10, 11: adb knowledge stays in src/drivers/android/; the gate is the one place that allows a physical command; every physical wait is bounded.
  • testing.md rules 1–4: the "never" lines below assert over every physical method's calls, not one.
Tests

Seam: AndroidDriver.create with ScriptedProcessRunner, FakeTcpProbe and MemoryFilesystem, modelled on src/drivers/android/index.test.ts. A gate test rejects FakeTcpProbe.send with an error whose code is ECONNREFUSED, or another code. FakeTcpProbe records each send's port and payload today (src/ports/tcp-probe.ts:102-105) and gives every send one reply; it gains the send's limit and a reply per send, so a test can change the server's version between two commands of one call. The unsetEnv merge is tested in src/daemon/dispatcher.test.ts. Phone lane: withDaemon({ driver: "real" }) and the HTTP device.exec route, modelled on e2e/slow-android-smoke.test.ts and e2e/device-exec.test.ts.

  • right before every adb command a physical method sends (inspectPhysical, listPresentPhysical, reclaim, and the command passthrough returns), the driver sends one host:version to port 5037 with a 5000 ms limit, and it sends nothing else to 5037
  • estimate and leaseEnvironment on a physical device send nothing to 5037 and start no process
  • a server that turns to another protocol between two commands of one call gets no later command: inspectPhysical after adb devices -l is refused not-connected naming both versions and runs no getprop; listPresentPhysical answers that serial not present not-present; reclaim after its first uninstall rejects and runs no second uninstall
  • two passthrough calls on a physical device send two host:version: nothing keeps the server's answer. After advisories() saw a mismatch, a passthrough whose own check matches returns its command; after advisories() saw a match, a passthrough whose own check sees another version is refused
  • no physical method, and no passthrough result, ever runs kill-server or start-server against 5037, and dispose() sends nothing to 5037
  • with a default server of another protocol: listPresentPhysical answers every given serial not present not-present, inspectPhysical is refused not-connected with a message naming both protocol numbers, reclaim rejects, and none of them starts a process after adb version
  • a host:version reply that is not OKAY0004 plus four hex digits, an empty reply (a timeout), a send that rejects with an error other than ECONNREFUSED, and an adb version with no protocol number, each block physical commands like a mismatch
  • a send that rejects with ECONNREFUSED (nothing listens on 5037) lets physical commands run
  • an adb version with no protocol number blocks physical commands even when send rejects with ECONNREFUSED
  • inspectPhysical is refused unreadable naming "Simlock's adb version" when adb version has no protocol number, and naming "the adb server's version on port 5037" when the reply is not a version, is empty, or send rejects with an error other than ECONNREFUSED
  • a server that matched again after a mismatch gets commands on the next call, and nothing restarts it
  • Simlock's adb version runs once across several physical calls; after a failed adb version, the next call runs it again
  • after the adb binary's modifiedAtMs or size changes, the next call runs adb version again and uses the new number
  • advisories() returns adb-protocol-mismatch naming both protocol numbers on a mismatch; "unreadable" for the server's version when the reply is not a version, is empty, or send rejects with an error other than ECONNREFUSED; "unreadable" for Simlock's version when adb version has no protocol number, with send rejecting ECONNREFUSED too; and nothing on a match or when send rejects with ECONNREFUSED
  • advisories() reports the mismatch on a driver that has never been asked about a physical device (no Android device enrolled)
  • with ANDROID_ADB_SERVER_ADDRESS, ADB_SERVER_SOCKET and ANDROID_SERIAL in the base environment, every physical command runs adb -P 5037 with ANDROID_ADB_SERVER_PORT=5037 and none of the three; every device command carries -s <serial>
  • inspectPhysical of a serial on a USB transport in state device returns that serial, ro.product.model, the API level, class phone, and the packages pm list packages -3 --user <id> lists for the id am get-current-user prints
  • inspectPhysical of a serial in state unauthorized is refused untrusted, and runs no command naming it
  • inspectPhysical of a serial only on a network transport (192.168.1.5:5555, an mDNS _adb-tls-connect._tcp name) is refused network-only with "only USB-connected devices can be enrolled", and runs no command naming it
  • inspectPhysical of emulator-5554 listed on the default server is refused network-only with "only USB-connected devices can be enrolled", and runs no command naming it
  • the transport decides, not the shape: a serial shaped like host:port on a USB transport is inspected, and a USB-shaped serial on a network line is refused
  • inspectPhysical of a serial not listed, or listed offline, is refused not-connected
  • inspectPhysical whose adb devices -l fails or passes 30 s, or whose getprop, user id or package read fails after the device was found, is refused unreadable naming the read, writes nothing, and does not reject
  • inspectPhysical whose am get-current-user prints 0; rm -r /sdcard, nothing, or 1234567 is refused unreadable, and runs no pm list packages
  • listPresentPhysical answers a given serial on USB in state device present with its API level, an unauthorized one not present untrusted, and an offline, network-only or missing one not present not-present
  • listPresentPhysical gives no entry, and runs no command, for a USB phone that is not among the given serials
  • listPresentPhysical answers every given serial not present not-present, and does not reject, when adb devices -l fails or has no exit code
  • listPresentPhysical answers a present serial whose getprop fails not present not-present
  • reclaim uninstalls, for the current user, each package off the enrollment list, uninstalls no listed package, reports listed packages not installed as missingApps, and returns strategy uninstall
  • reclaim with nothing off the list runs no uninstall
  • reclaim whose am get-current-user prints 0; rm -r /sdcard, nothing, or 1234567 rejects, and runs no pm list packages and no pm uninstall
  • reclaim whose user id read fails, or passes 30 s, rejects, and runs no pm list packages and no pm uninstall
  • reclaim whose first pm list packages -3 fails, passes 30 s, or has a bad line rejects, runs no uninstall, and never resolves with an empty list
  • reclaim rejects, and runs no later uninstall, when an uninstall prints Failure […], exits non-zero, or has no exit code
  • reclaim rejects when the second listing fails, passes 30 s, or has a bad line
  • reclaim rejects when the second listing still holds a package off the enrollment list
  • a package line with a character outside letters, digits, _ and ., and a bare package:, each fail the listing, and no uninstall runs
  • a package listing with \r\n line ends and blank lines reads the same packages as one with \n and none
  • reads pass a 30 000 ms limit and uninstalls a 60 000 ms limit to the process runner
  • while a physical adb devices -l hangs, listManaged for emulators resolves
  • while an emulator call holds #withDeviceLock (index.ts:2041) and a PortAllocator port (index.ts:2068), listPresentPhysical and passthrough on a physical device resolve
  • passthrough("adb", <physical device>, ["shell", "getprop"]) resolves to adb -P 5037 -s <serial> shell getprop with ANDROID_ADB_SERVER_PORT=5037
  • passthrough on a physical device refuses a caller argument before the subcommand (-s, -t, -d, -e, -P, -H, -L, --one-device, --reply-fd), each of kill-server, start-server, server, nodaemon, connect, disconnect, pair, reconnect, tcpip, usb, detach and reboot-bootloader as the subcommand, reboot followed by each of bootloader, sideload, sideload-auto-reboot, recovery, fastboot, and bare shell with no terminal
  • passthrough on a physical device runs shell dumpsys usb, shell echo connect and reboot with no argument: a refused word after the subcommand is an operand, and a plain reboot is not refused
  • passthrough on a physical device is refused with PassthroughRefusedError when its check sees another version (the message names both), or cannot read a version (the message names which), and the process runner starts nothing
  • passthrough on a physical device returns unsetEnv ANDROID_ADB_SERVER_ADDRESS, ADB_SERVER_SOCKET and ANDROID_SERIAL; with no device it returns none
  • (dispatcher, src/daemon/dispatcher.test.ts) device.exec with an execEnv holding a key the command's unsetEnv names spawns without that key, and keeps every other key
  • passthrough with no device still runs on Simlock's own server (today's index.test.ts passthrough tests, unchanged and green)
  • passthrough with no device, or with an emulator device, sends no host:version
  • estimate of a reclaim for a physical spec is 90 000 ms at both clean levels; for an emulator spec it is unchanged
  • (phone lane) simlock device add --platform android $SIMLOCK_PHYSICAL_ANDROID enrolls it with the model and API level adb shell getprop reports and as many apps as pm list packages -3 lists for the current user
  • (phone lane) simlock lease --platform android --physical grants that serial with ANDROID_SERIAL set to it and ANDROID_ADB_SERVER_PORT=5037, and device.exec adb shell getprop ro.serialno over HTTP prints the serial
  • (phone lane) after installing $SIMLOCK_PHYSICAL_ANDROID_APK with plain adb -s and releasing, the device is ready, the APK's package is gone, and every enrolled package is still installed
  • (phone lane) the plugged-in phone stays ready across three probe intervals
  • (phone lane) with a short exec.timeoutMs (configOverrides), a device.exec adb shell sleep 60 on the leased phone ends EXEC_TIMEOUT, and the adb server listening on 5037 still runs with the pid it had before the command
  • (phone lane, manual file) with a short health.probeIntervalMs, the phone unplugged when the test asks in its log becomes absent with absentReason not-present within 150 s, and plugged back becomes ready
  • (phone lane) scripts/slow-e2e.sh --physical e2e/slow-android-physical.test.ts and scripts/slow-e2e.sh --physical-manual e2e/slow-android-physical-manual.test.ts, each run with SIMLOCK_PHYSICAL_ANDROID unset, skip, and the skipped title names it; the same with SIMLOCK_PHYSICAL_ANDROID_APK unset

Done when

  • With a default adb server of another protocol version, the Android driver runs no command on a physical device, device add is refused not-connected naming both versions, device.exec on a physical lease is refused naming both versions, and advisories() names both versions even with no Android device enrolled (driver tests above).
  • A check runs right before every physical adb command, not once per call: a server that changes protocol between two commands of one call gets no later command (driver tests above).
  • No physical call stops or starts the default server, and dispose() leaves it (driver tests above).
  • A hung phone call does not delay an emulator call (driver test above).
  • reclaim with a bad or unreadable user id, or a failed package listing, rejects and uninstalls nothing (driver tests above).
  • On a Mac with an Android SDK, one Android phone on USB with USB debugging authorized, SIMLOCK_PHYSICAL_ANDROID set to its serial and SIMLOCK_PHYSICAL_ANDROID_APK to an APK: device add enrolls it, a --physical lease gets it with ANDROID_SERIAL, device.exec reaches it, after release the installed app is gone and every enrolled app remains, and a timed-out device.exec leaves the adb server on 5037 running with the same pid. Proved unattended by scripts/slow-e2e.sh --physical e2e/slow-android-physical.test.ts. This needs real hardware.
  • On the same machine, with a person at the phone, the phone unplugged on the test's prompt becomes absent (not-present), and plugged back becomes ready. Proved by scripts/slow-e2e.sh --physical-manual e2e/slow-android-physical-manual.test.ts with SIMLOCK_PHYSICAL_PERSON=1. This needs real hardware and a person; the hardware verifier reports it unavailable (needs a person), and the maintainer runs it.
  • scripts/slow-e2e.sh --physical e2e/slow-android-physical.test.ts and scripts/slow-e2e.sh --physical-manual e2e/slow-android-physical-manual.test.ts, each run with SIMLOCK_PHYSICAL_ANDROID or SIMLOCK_PHYSICAL_ANDROID_APK unset, skip, and the skipped title names the unset variable. This needs no phone.
  • A timed-out or failed host:version, or a binary update to another adb protocol, blocks physical commands, and a daemon env's ANDROID_ADB_SERVER_ADDRESS never reaches a physical command (driver and dispatcher tests above).
  • docs/CLI.md lists the Android physical-device prerequisites and the adb-protocol-mismatch advisory; toolchain.md has the Android phone row, with the person check on the manual file; KNOWN-PITFALLS.md has the entry above.

Out of scope

  • Routing physical requests and device.exec through a gateway (task 6).
  • Wi-Fi debugging, work profiles and other Android users.
  • Restarting or fixing a default server of another protocol version.
  • Guarding against another adb client restarting the default server.
  • The iOS physical handler (task 4).

Depends on

  • #435
  • #440

Approval

  • Approved for delivery

Written by an agent.

Lingua principale
TypeScript
Stelle
19
Fork
1
Merge medio
6h 14m
PR unite (30g)
141

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di callstackincubator/simlock

Tutte le issue di callstackincubator/simlock

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.