Files
BikeControl/src-tauri/Cargo.toml
T
dtourolleandClaude Opus 5 0679a1f524 Ride on Android: the same BLE stack, over JNI
G-4 said port to Android without rewriting the core, and nothing in
crates/core, crates/ble or crates/fit needed touching (NFR-5) — the
Android work is two files of glue and a Gradle project.

btleplug's Android backend is a hybrid crate: the GATT work happens in
Java and Rust drives it over JNI. `platform::init` has to run once with a
JNIEnv, and it cannot come from Rust's own startup — JNI resolves classes
with the calling thread's class loader, and a thread Rust spawned has
only the bootstrap loader. So MainActivity.onCreate calls into
src/android.rs, before super.onCreate: TauriActivity's super chain
synchronously starts the thread that runs `run()`, which builds AppState
and starts scanning while we are still in onCreate. Lose that race and
droidplug's global_adapter() — an `expect` — panics inside the scan task,
silently, for the life of the process.

Failing soft here is not enough for the same reason, so init sets a READY
flag and devices.rs asks before every call in. Bluetooth switched off at
launch then reads as an ordinary "no adapter", which the connection
screen already knows how to show, and onResume retries so switching it on
and coming back works.

The Java half is not a maven dependency. Upstream tells you to publish a
0.1.1-SNAPSHOT artifact to mavenLocal by hand, which no CI runner can
reproduce and which drifts from the crate silently — the failure is a
NoSuchMethodError at the first scan, not a build error. Instead
sync-android-sources.sh lifts the classes out of the btleplug and
jni-utils crate sources at exactly the versions in Cargo.lock, so a
mismatch is impossible by construction.

gen/ stays generated and untracked, so everything hand-written lives in
src-tauri/android/ and is copied back after each `tauri android init`.
check-android-sources.sh fails the build if a source exists only under
gen/ or differs from its tracked copy: both are files git has never seen
and the next init deletes, and the resulting APK builds, installs, and
behaves as though they were never written.

Permissions are split at API 31, because asking for one the platform does
not know is a permanent denial. neverForLocation on BLUETOOTH_SCAN is a
promise we can keep honestly: every scan filters by service UUID, so no
location permission is needed on Android 12+.

Also: tracing to logcat, since Android has no stdout and the default
writer drops every line into a closed fd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:52:33 +02:00

56 lines
1.9 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"