Skip to content

Feat/android jni usb layer#35

Merged
s00d merged 36 commits into
mainfrom
feat/android-jni-usb-layer
Jul 8, 2026
Merged

Feat/android jni usb layer#35
s00d merged 36 commits into
mainfrom
feat/android-jni-usb-layer

Conversation

@s00d

@s00d s00d commented Jul 8, 2026

Copy link
Copy Markdown
Owner

3.0.0 (2026-07-08)

Major v3 release: Channel-based watch() API, native AT exchange / FIFO queue, unified RX hub on desktop and Android, and a hard cut from vendored Java usb-serial-for-android to the pure-Rust android-usb-serial crate (nusb).

Breaking Changes

  • Streaming API: startListening() / listen() / disconnected() / cancelListen() removed. Use watch({ onData, onDisconnect?, onError?, onUrc? })SerialEvent over Tauri Channel (desktop + Android).
  • Capabilities: SerialPort.getCapabilities() (transport, platform, version); commands capabilities, watch, unwatch replace start_listening / stop_listening.
  • Removed: available_ports_direct — use available_ports().
  • Removed AtCommandQueue / port.at: Use sendAt(), sendAtPhases(), sendSmsPdu(), cancelAt(), configureAtSession().
  • onUrc removed from AtSessionOptions: Use watch({ onUrc }) for live URC lines.
  • exchange() returns ExchangeResponse, not Uint8Array. Use .raw for bytes.
  • clearRx: true maps to purge; default is now drain when unset.
  • Android USB stack: Vendored android/usbserial Java tree removed. Kotlin no longer runs serial I/O commands on the main thread — only UsbFdBridge (enumerate, permission, fd). All drivers and bulk I/O live in Rust (android-usb-serial + nusb).
  • Rust module layout: Removed compat top-level modules (desktop_api, mobile_api, port_rx_hub, exchange_runtime, …). Use api::serial, hub, exchange, port, state, android.
  • guest-js: AtCommandResult.raw / ExchangeResponse.raw are Uint8Array; timedOut is a native boolean.
  • rust-version: 1.79 minimum.

Migration (v2 → v3)

v2 v3
startListening() + listen(fn) watch({ onData: fn })
disconnected(fn) watch({ onDisconnect: fn })
cancelListen() / stopListening() handle.unwatch()
port.at.enable() / enqueue() sendAt() / sendAtPhases()
Kotlin UsbBridge + Java drivers UsbFdBridge fd → Rust driver_host

Android app requirements: uses-feature android.hardware.usb.host, device_filter.xml, runtime USB permission before openDevice(). Do not claimInterface() in Kotlin before handing fd to Rust. See crates/android-usb-serial/README.md.

Features

  • android-usb-serial crate: Pure Rust USB serial on Android via nusb; 567 golden parity fixtures; drivers for FTDI, CP21xx, CH34x, PL2303, CDC-ACM, GSM modem, Chrome CCD (20b54c0).
  • UsbFdBridge: Kotlin provides USB fd only; Rust driver_host owns probe, claim, drivers, RX reader, and I/O.
  • Unified api::serial: Single serialport::SerialPort facade on desktop and Android; PortRxHub poll-loop on both platforms (4071ff6).
  • Rust-first Android JNI (29faa29): bulk-IN reader thread, chunked write, CMUX virtual paths through Rust session.
  • Unified RX hub (desktop + Android): Single consumer per port; watch, exchange, read, and drain share one hub (7024fc0, caa5700).
  • take_idle_bytes: Stale RX in the hub idle buffer is replayed into the next exchange after write.
  • Native FIFO queue (Rust + Android): All exchange / AT jobs on one port serialize in FIFO order; parallel invokes wait instead of "Exchange already in progress" (e1eb63f).
  • Native exchange / cancel_exchange: Write + read-until terminators, idle silence, wall timeout, max response size; structured ExchangeResponse (status, lines, solicitedBody, urcLines, raw).
  • Line-framed AT completion: Final line OK / ERROR / +CME ERROR / +CMS ERROR; completionMode: 'substring' for legacy/binary.
  • rxPrepare: Default drain; purge opt-in; none unchanged.
  • watchAvailablePorts() / watch_ports: Hotplug via Channel — snapshot, then added / removed (desktop poll; Android USB attach/detach).
  • open() canonical path: Returns session key (Android device path / device#N for multi-port).
  • CMUX virtual exchange: Paths physical#dlci=N routed through Rust CMUX session like desktop.
  • usb-driver-tester: Standalone hardware self-test app under examples/usb-driver-tester/.
  • guest-js: Modular v3 SDK; auto-reconnect restores open() + watch() after disconnect.
  • macOS: available_ports({ singlePortPerDevice: true }) — one path per device (prefers /dev/cu.*).
  • Extended AT grammar: Vendor prefixes, V.250 finals, derive_solicited_prefixes(command); ExchangeDemux for live URC before echo.
  • pauseWatch default false — watch stays on during AT; pass pauseWatch: true for legacy behavior.

Bug Fixes

  • Android CH340 / weak OTG: Bulk IN reader starts after line/DTR setup; in-flight URBs reduced to 2; clearer detach reason in logs.
  • Android enumerate: Kotlin exports interfaces[]; Rust expands multi-port paths (device#N) via ProbeTable without opening fd.
  • Android write after listen: EndpointPair::write no longer re-opens bulk IN owned by SerialReader (endpoint already in use).
  • Android Kotlin fd bridge: Removed pre-claimInterface (fixes io interface is busy with nusb detach_and_claim).
  • Android logcat: Rust plugin logs use tag SerialPlugin via __android_log_write.
  • Android: USB permission PendingIntent uses FLAG_MUTABLE on API 31+; detach + IO errors → SerialEvent::Disconnect on Channel (#27).
  • Android: cancel_exchange wakes hub waiter; CMUX virtual cancel clears DLCI TX queue (c6c94dd).
  • Android: JNI exceptions, session path re-key, teardown leaks (e9fc307).
  • Android: Serialize fail/shutdown on dedicated usb-io thread (cd74b84).
  • desktop: write / write_binary flush via write_all (#29).
  • desktop: Unblock hub drain when watch is active (55f1cb3).
  • desktop: Lock order, Opening-state open, async enable_mux (3a4bd88).
  • desktop (Windows): Enrich truncated USB serial_number from WMI (#23).
  • Hub / watch: lock_or_recover in watch_registry and hub channel paths (2071e15); panic/poison hardening (8672de9).
  • TX queue: Errors no longer halt the port queue until reopen (e1eb63f).
  • guest-js: readBinary requires open port; watch-preserving change() (9b22af7).

Build / CI / Docs

s00d and others added 29 commits July 7, 2026 15:07
Replace @command SerialPortManager with UsbBridge/UsbNative JNI path,
vendored usb-serial 3.10.0, SIOM lifecycle fixes, and unified watch/exchange API.

Co-authored-by: Cursor <cursoragent@cursor.com>
Apply cubic PR #34 feedback: route SIOM onError through io executor,
shutdown executor after cleanup task, reject I/O after shutdown.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Stop tracking IDE metadata and npm lockfiles; publish correct LICENSE paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove permanent halted flag; stop_on_error only aborts the current at_phases
batch. clear_halt resets drain waiters after cancel_exchange.

Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid map locks across I/O; open blocks outside serialports mutex with
Opening guard; eager RX hub start; virtual watch/close without AB deadlock.

Co-authored-by: Cursor <cursoragent@cursor.com>
Route desktop/mobile reads through RxHubShared (read_slot, idle, exchange
waiter-before-write). Remove port_io_yield; hub stop checks stop_rx during
poll reads; replay idle bytes into active exchanges; fix exchange_waiter
re-entrant lock in feed_bytes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove Kotlin/Rust poll-read path; Android RX feeds MobileRxHub via SIOM.
Mobile exchange/read use hub read_request like desktop.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add lock_or_recover for background-thread mutexes; drop unsafe Send/Sync on
Error and release-profile panic=abort.

Co-authored-by: Cursor <cursoragent@cursor.com>
…rdown leaks

with_local_frame + Java exception clear; open returns canonical session path;
Android 14 PendingIntent flags; CoalescingRxSink shutdown on close; hub
shutdown in close_all; singlePortPerDevice parity; remove mobile_response.

Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve onUrc across watch rebuilds; exchange raw is Uint8Array with
timedOut flag; change() reopens and re-establishes watch; isOpen guards
on control-line writes; apply canonical path from open().

Co-authored-by: Cursor <cursoragent@cursor.com>
Unify desktop mocks in mock_serial.rs; add hub read_slot/idle/restart and
PTY watchdog tests; JS exchange/mux/log-level suites; Kotlin coalescer
cleanup test.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document Uint8Array raw, timedOut, canonical open path, RX hub testing,
UsbBridge/UsbPortSession, and auto-reconnect TOC updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… field

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…reconnect edges

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…droid

Reorganize the plugin core into focused modules so desktop and mobile
targets share exchange/hub logic while Android JNI lives under android/.
Gate desktop-only unit tests and CMUX session tests for cargo ndk.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expose testHarness* JNI entry points on MobileBridge and UsbBridge
integration-test helpers. Move FakeUsb/RecordingRxSink to sharedTest
for unit and instrumented reuse.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add stable instrumented tests for RX/TX/error chains through JniSerialRxSink,
a Parcel-based FakeUsb for on-device runs, and sync script into gen/android.

Co-authored-by: Cursor <cursoragent@cursor.com>
Run connectedUniversalDebugAndroidTest on macOS with API 34 emulator
and cross-check the Windows MSVC target in a dedicated job.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document JNI integration test workflow, sharedTest layout, and unreleased
changelog entries for the Rust reorg and Android test harness.

Co-authored-by: Cursor <cursoragent@cursor.com>
Checked-in copy of JNI integration tests for Gradle/CI without a
pre-build sync step; canonical sources remain in android-integration/.

Co-authored-by: Cursor <cursoragent@cursor.com>
Return Ok(0) from poll_read_port on benign read timeout so hub_loop
can finish rx_prepare drain and AT exchange does not hang with watch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace vendored usb-serial-for-android with the android-usb-serial
crate (nusb drivers). Kotlin keeps only UsbFdBridge for enumerate,
permission, and fd handoff; Rust owns claim, I/O, and multi-port probe.

Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt
#	package-lock.json
#	package.json
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

…-specific tests for closing ports and CMUX session routing
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Install aarch64-linux-android for host matrix cargo check/clippy.
Move android-integration off Apple Silicon macOS (emulator HVF unsupported)
to ubuntu KVM with x86_64 AVD and a prebuild/test script split.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

pnpm snapshots file: deps at install; dist-js appears only after
rollup, so example vue-tsc missed types in CI. Relink after build.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Golden parity links serialport/nusb on linux-gnu and needs -ludev;
apt was running after the host cargo test step.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Keep PTY master draining while exchange waits so Linux CI does not
tear down with Broken pipe or starve the RX hub on concurrent write.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Keep Robolectric, Android cargo check/clippy, and local scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Keep JNI harness build/sync in CI; device androidTest stays local.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@s00d s00d merged commit 6a70397 into main Jul 8, 2026
0 of 4 checks passed
@s00d s00d deleted the feat/android-jni-usb-layer branch July 8, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant