SerialPlugin → UsbFdBridge (Kotlin) → openDeviceFd / enumerateJson
UsbNative (JNI) ↔ fd_bridge.rs ↔ driver_host.rs ↔ android-usb-serial (nusb)
RX: Rust reader thread → PortRxHub poll-loop (same as desktop)
Kotlin keeps UsbDeviceConnection and the raw fd. Rust duplicates the fd (dup), opens nusb::Device::from_fd, claims interfaces, and runs vendor drivers from the android-usb-serial crate. There is no vendored usb-serial-for-android Java tree in this repo.
- Declare USB host in the app manifest (
uses-feature android.hardware.usb.hostoptional). - Ship a
device_filter.xmlaligned withdevice_filter.xml/ProbeTable::default_table(). - Request runtime USB permission before open (the plugin uses
PendingIntent.FLAG_MUTABLE+setPackage()).
Requires JDK 17:
export JAVA_HOME=$(/usr/libexec/java_home -v 17) # macOS
cd android && ./gradlew testStable sources: examples/serialport-test/android-integration/.
Uses FakeTransport via android-test-harness (no Kotlin USB fakes).
cd examples/serialport-test
pnpm android:integration-testDebug harness JNI (test_harness.rs): testHarnessReset, testOpenFakePort, testFakeInjectRx, testFakeTakeTx, testFakeInjectError, testHubBufferedLen, testInvokeWrite, testRegistryHasPort.
| File | Role |
|---|---|
SerialPlugin |
Tauri plugin load → UsbFdBridge |
UsbFdBridge |
enumerate, permission, fd open/close, attach/detach |
UsbNative |
JNI for enumerate + fd lifecycle |
MobileBridge |
JNI callbacks (onUsbError, onDeviceDetached, port list change, …) |
Rust drivers are verified against frozen JSON fixtures (crates/android-usb-serial/tests/fixtures/).
cargo run -p android-usb-serial --features fake-transport --bin golden_record
cargo test -p android-usb-serial --features fake-transport --test golden_paritySee docs/golden-recorder-archive/README.md for the retired JVM recorder workflow.
Logcat: adb logcat -s UsbFdBridge SerialPlugin RustStdoutStderr
exchange/atonphysical#dlci=N: Routed through the Rust CMUX session (same as desktop).cancel_exchange: Sets the virtual cancel flag, fails the active DLCI waiter, and clears the virtual TX queue.rx_prepare: drain: Uses the shared RX hub drain before write (idle bytes may still be replayed viatake_idle_bytes).