Make the Android BLE backend fail loudly and recoverably

Three defects on the path between MainActivity and the scan loop, all of
which presented as "no trainer found".

initBtleplug ran after super.onCreate, which is a race rather than a
clean ordering bug: the super chain dispatches Rust.create(), and tao's
ndk_glue spawns a thread to run `run()` on. That thread builds the
AppState and starts the scan loop concurrently. Reaching btleplug first
hits droidplug's global_adapter(), which is an `expect` — the scan task
panics and scanning is dead for the process, silently and only on some
phones. Initialising before super.onCreate means the race cannot be lost.

The same panic was reachable without any race, because init failure was
logged and shrugged off while every later call still went through to
`expect`. Failing soft is right; it just needed READY, so the call sites
can produce an ordinary "no adapter" instead of taking the task down
(NFR-4). MainActivity retries the init on resume, which is idempotent, so
a rider who launched with Bluetooth off recovers by going to Settings.

Neither of those covers a radio the rider switches off, which btleplug
does not model at all: getDefaultAdapter() returns a disabled adapter
whose scans just find nothing. MainActivity now watches
ACTION_STATE_CHANGED — the quick-settings shade never fires onResume —
and pushes the state to Rust, with requestBluetoothEnable coming back the
other way so the connection screen can offer the system dialog rather
than describing an empty room. Tri-state on purpose: unknown is not off,
or a rider with a working radio gets told to switch it on at launch.

Also: the adapter hint told Android riders to check BlueZ.

Verified on debug and release APKs for aarch64. Release matters
separately here — every one of these classes is reached only by name over
JNI, so R8 would strip or rename the lot and the failure would appear
only in a shipped build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 20:06:03 +02:00
co-authored by Claude Opus 5
parent 8fcee06d84
commit aa99b83c40
6 changed files with 234 additions and 11 deletions
@@ -18,7 +18,14 @@
-keepclassmembers class * extends android.bluetooth.BluetoothGattCallback { *; }
-keepclassmembers class * extends android.bluetooth.le.ScanCallback { *; }
# Our own JNI entry point, called from MainActivity as an `external fun`.
# Our own JNI entry points, called from MainActivity as `external fun`.
-keepclasseswithmembernames class paris.tourolle.bikecontrol.MainActivity {
native <methods>;
}
# The one call that goes the other way. Rust resolves it by name and signature
# at runtime, so R8 renaming it would leave the "turn Bluetooth on" button doing
# nothing in release builds and working perfectly in debug ones.
-keepclassmembers class paris.tourolle.bikecontrol.MainActivity {
public void requestBluetoothEnable();
}