Scanning worked on the tablet and connecting did not, and the reason was
one missing call.
btleplug's Android backend hands Rust its results as Java future objects
wrapped by jni-utils, and every one of those wrappers resolves its class
through a cache. Only `jni_utils::init` fills that cache. droidplug does
not call it — its own `init` registers droidplug's classes and assumes
the application has already done jni-utils' — and nothing else did
either, so the cache held droidplug's seven classes and none of
jni-utils' ten.
That split the BLE stack in half exactly where the symptom appeared.
Scan results arrive on a plain JNI callback and never touch a future, so
scanning was perfect. `connect` is the first path that awaits one, and
`JFuture::from_env` unwraps `get_class("…/future/Future")` — None —
straight into a panic on the runtime thread, taking the trainer
supervisor task with it. What reached the log was "trainer command
dropped — supervisor queue full or closed", which describes the corpse
rather than the cause; the panic itself only appeared under
RustStdoutStderr, and only because the Android target routes stdout to
logcat.
The GATT link was fine throughout, which is what made this confusing to
read: Android logged `onClientConnectionState … status=0 connected=true`
for the trainer a second *after* the task waiting for it had died.
Pinned to 0.1.1 deliberately. The cache is a static inside jni-utils, so
a second copy at a different version is a second, empty cache and the
panic comes back.
Verified on the tablet (Android 16, aarch64): both Click pods connect on
their own, and the trainer reaches state=Controlling with its FTMS
capabilities read back. Zero panics in the process log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
2.2 KiB
TOML
61 lines
2.2 KiB
TOML
[package]
|
|
name = "bikecontrol-app"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
repository.workspace = true
|
|
description = "BikeControl desktop shell: Tauri commands and the event bridge to the UI"
|
|
|
|
[lib]
|
|
# Tauri v2 convention: the app lives in a lib so the same code can be reused by
|
|
# the Android/iOS entry points later (G-4).
|
|
name = "bikecontrol_app_lib"
|
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
|
|
[build-dependencies]
|
|
tauri-build = { version = "2", features = [] }
|
|
|
|
[dependencies]
|
|
bikecontrol-core = { workspace = true }
|
|
bikecontrol-ble = { workspace = true }
|
|
bikecontrol-fit = { workspace = true }
|
|
|
|
tauri = { version = "2", features = [] }
|
|
tauri-plugin-dialog = "2"
|
|
|
|
uuid = { workspace = true }
|
|
chrono = { workspace = true }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
serde_yaml_ng = { workspace = true }
|
|
roxmltree = { workspace = true }
|
|
tokio = { workspace = true }
|
|
anyhow = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
tracing = { workspace = true }
|
|
tracing-subscriber = { workspace = true }
|
|
|
|
# Desktop-only: the crate is a `compile_error!` on anything that is not
|
|
# Windows/Linux/macOS, and the mobile entry points (G-4) have their own
|
|
# platform APIs for this. `wakelock.rs` degrades to a no-op there. On Android
|
|
# the equivalent is FLAG_KEEP_SCREEN_ON, set in MainActivity.kt.
|
|
[target.'cfg(any(windows, target_os = "linux", target_os = "macos"))'.dependencies]
|
|
keepawake = "0.6.0"
|
|
|
|
# Android-only (G-4). `src/android.rs` is the whole JNI surface: btleplug's Java
|
|
# backend has to be initialised from a Java thread, and `tracing` has to be
|
|
# pointed at logcat.
|
|
#
|
|
# `jni` is pinned to 0.19 because that is what btleplug 0.11 uses, and
|
|
# `platform::init` takes a `&JNIEnv` from *that* version — a 0.21 JNIEnv is a
|
|
# different type and would not compile.
|
|
[target.'cfg(target_os = "android")'.dependencies]
|
|
btleplug = { workspace = true }
|
|
jni = "0.19"
|
|
libc = "0.2"
|
|
# Pinned to the version btleplug 0.11 resolves to, and it must stay that way:
|
|
# the class cache `init` populates is a static *inside this crate*, so a second
|
|
# copy at a different version would be a second, empty cache and `connect`
|
|
# would go back to panicking.
|
|
jni-utils = "0.1.1"
|