Feat/android jni usb layer#35
Conversation
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
|
|
|
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
|
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>
|
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>
|
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>
|
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>
|
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>
|
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>
|
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. |
3.0.0 (2026-07-08)
Major v3 release: Channel-based
watch()API, native ATexchange/ FIFO queue, unified RX hub on desktop and Android, and a hard cut from vendored Javausb-serial-for-androidto the pure-Rustandroid-usb-serialcrate (nusb).Breaking Changes
startListening()/listen()/disconnected()/cancelListen()removed. Usewatch({ onData, onDisconnect?, onError?, onUrc? })→SerialEventover TauriChannel(desktop + Android).SerialPort.getCapabilities()(transport,platform,version); commandscapabilities,watch,unwatchreplacestart_listening/stop_listening.available_ports_direct— useavailable_ports().AtCommandQueue/port.at: UsesendAt(),sendAtPhases(),sendSmsPdu(),cancelAt(),configureAtSession().onUrcremoved fromAtSessionOptions: Usewatch({ onUrc })for live URC lines.exchange()returnsExchangeResponse, notUint8Array. Use.rawfor bytes.clearRx: truemaps topurge; default is nowdrainwhen unset.android/usbserialJava tree removed. Kotlin no longer runs serial I/O commands on the main thread — onlyUsbFdBridge(enumerate, permission, fd). All drivers and bulk I/O live in Rust (android-usb-serial+ nusb).desktop_api,mobile_api,port_rx_hub,exchange_runtime, …). Useapi::serial,hub,exchange,port,state,android.AtCommandResult.raw/ExchangeResponse.rawareUint8Array;timedOutis a nativeboolean.rust-version: 1.79 minimum.Migration (v2 → v3)
startListening()+listen(fn)watch({ onData: fn })disconnected(fn)watch({ onDisconnect: fn })cancelListen()/stopListening()handle.unwatch()port.at.enable()/enqueue()sendAt()/sendAtPhases()UsbBridge+ Java driversUsbFdBridgefd → Rustdriver_hostAndroid app requirements:
uses-feature android.hardware.usb.host,device_filter.xml, runtime USB permission beforeopenDevice(). Do notclaimInterface()in Kotlin before handing fd to Rust. Seecrates/android-usb-serial/README.md.Features
android-usb-serialcrate: 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; Rustdriver_hostowns probe, claim, drivers, RX reader, and I/O.api::serial: Singleserialport::SerialPortfacade on desktop and Android;PortRxHubpoll-loop on both platforms (4071ff6).watch,exchange,read, and drain share one hub (7024fc0, caa5700).take_idle_bytes: Stale RX in the hub idle buffer is replayed into the nextexchangeafter write.exchange/ AT jobs on one port serialize in FIFO order; parallel invokes wait instead of"Exchange already in progress"(e1eb63f).exchange/cancel_exchange: Write + read-until terminators, idle silence, wall timeout, max response size; structuredExchangeResponse(status,lines,solicitedBody,urcLines,raw).OK/ERROR/+CME ERROR/+CMS ERROR;completionMode: 'substring'for legacy/binary.rxPrepare: Defaultdrain;purgeopt-in;noneunchanged.watchAvailablePorts()/watch_ports: Hotplug via Channel —snapshot, thenadded/removed(desktop poll; Android USB attach/detach).open()canonical path: Returns session key (Android device path /device#Nfor multi-port).exchange: Pathsphysical#dlci=Nrouted through Rust CMUX session like desktop.usb-driver-tester: Standalone hardware self-test app underexamples/usb-driver-tester/.open()+watch()after disconnect.available_ports({ singlePortPerDevice: true })— one path per device (prefers/dev/cu.*).derive_solicited_prefixes(command);ExchangeDemuxfor live URC before echo.pauseWatchdefaultfalse— watch stays on during AT; passpauseWatch: truefor legacy behavior.Bug Fixes
interfaces[]; Rust expands multi-port paths (device#N) viaProbeTablewithout opening fd.EndpointPair::writeno longer re-opens bulk IN owned bySerialReader(endpoint already in use).claimInterface(fixesio interface is busywith nusbdetach_and_claim).SerialPluginvia__android_log_write.PendingIntentusesFLAG_MUTABLEon API 31+; detach + IO errors →SerialEvent::Disconnecton Channel (#27).cancel_exchangewakes hub waiter; CMUX virtual cancel clears DLCI TX queue (c6c94dd).write/write_binaryflush viawrite_all(#29).enable_mux(3a4bd88).serial_numberfrom WMI (#23).lock_or_recoverin watch_registry and hub channel paths (2071e15); panic/poison hardening (8672de9).readBinaryrequires open port; watch-preservingchange()(9b22af7).Build / CI / Docs
cargo fmt,clippy,pnpm check/build, Windowscargo check, Android integration emulator job (1aa1682, 2e70ecf).FakeTransport(412b4a5, be77e2a); Jest v3 lifecycle + Rust contract tests.examples/serialport-test/ANDROID.md,android-usb-serialAndroid usage (c2d1e23, b3cc7d2).android-test-harnessfeature wiresFakeTransportfor instrumented tests.