Krolyxon
Commit 8ec1f82

Branches

Refs →

budctldocsandroid.md

186 lines · 10.3 KB

Android app

budctl on the owner's phone, so the phone needs no other app to control the buds. It is a Kotlin app with budctl-core inside it: the same protocol code, tests and spec as the CLI and the desktop app.

This file is the plan. Each step says when it is done. The phone checkpoints go into the verification log, like the desktop app's.

Why Kotlin as well as Rust

What runs where

android/                          Kotlin, built with Gradle
  ui/           Compose: the desktop app's pages, in a phone layout
  Bluetooth.kt  paired devices; a BluetoothSocket as send / recv(timeout)
  Core.kt       the Rust library: commands in, events out, as JSON
        │ JNI
crates/budctl-android             cdylib: the JNI exports, a Transport over Bluetooth.kt
        │
crates/budctl-core                the protocol, unchanged: frame, link, codec, devices, worker

The boundary

Rust exports, called from Kotlin (Core.kt):

Function Does
start(platform, idleMs, debug) starts the worker; platform is Kotlin's side, below
command(json) queues one command; its event comes back through platform.event
stop() ends the worker, which closes any session

Kotlin's side, called on the worker's thread:

Method Does
pairedDevices() the paired devices as JSON: address, name, alias, connected, service UUIDs
connect(address, uuid) opens the RFCOMM socket to that service, or fails with a kind (busy, not-connected, failed) and a message
event(json) one worker event, for the UI
log(line) a session or debug line, for logcat and the in-app log

The link that connect returns has send(bytes), recv(timeoutMs) (the bytes, or null once the time runs out) and close().

Bluetooth on Android

Written from Android's API reference. No other app's code is read for it (rule 2).

Steps

  1. Toolchain: Android Studio, which brings the SDK (~/Android/Sdk), the emulator and a JDK for Gradle (/opt/android-studio/jbr). The NDK, from Studio's SDK Manager. rustup target add aarch64-linux-android x86_64-linux-android and cargo install cargo-ndk. Done when the NDK is in ~/Android/Sdk/ndk and adb devices lists the emulator. The phone joins before A1: wireless debugging, paired with adb pair.
  2. The core builds for Android. bluez, transport::{rfcomm, sdp, socket}, scan and connect go behind cfg(target_os = "linux"), and libc and zbus become Linux-only dependencies. PairedDevice moves to the crate root, since Android fills it too. CI checks the core for aarch64-linux-android. Done when cargo clippy -p budctl-core --target aarch64-linux-android -- -D warnings passes and nothing changes on Linux.
  3. The worker moves into core (budctl_core::worker), with its tests. A Report trait takes its events and log lines. The desktop app's worker.rs keeps only the iced subscription and the BlueZ connect. Done when the desktop app behaves the same and all tests pass.
  4. crates/budctl-android: the JNI exports, Deserialize for the commands and Serialize for the events, the Transport over Kotlin's link, and the pluggable debug output. Rule 5 needs an exception first (see Decisions). Done when cargo ndk -t arm64-v8a build -p budctl-android builds and the crate's tests (JSON round trips, a scripted link) pass on Linux.
  5. The Android project (android/): Gradle, with cargo ndk run from Gradle, Bluetooth.kt, Core.kt, and a plain screen with the status as text, the noise-control choice and the debug log. JVM tests cover the link's reader thread and timeouts. Done when A1 and A2 pass on the phone.
  6. The pages, built the way the desktop app was: first the look with demo buds, then live buds, then one page at a time (Overview, Equalizer, Gestures, Earbuds, Settings).
    • The look is the desktop app's: Nothing OS shapes and type in Material You colors. The colors come from Android's dynamic color (the phone's wallpaper palette), mapped to the same roles.
    • The same fonts, from crates/budctl-gui/fonts. Battery rings, dotted sliders and pill switches are drawn on Compose canvases, with motion.rs's timings.
    • Touch only: no vim keys.
    • Claude checks the look on the emulator, with the demo buds, from adb screenshots. Done when A3 passes for every page.
  7. Release. One version for everything: Gradle reads it from Cargo.toml. CI builds and tests the app on every push, and a tag adds a signed APK to the GitHub release. The signing key stays with the owner, as a CI secret. No Play Store. New versions come from the GitHub releases (an app such as Obtainium can watch them); budctl itself never checks. Done when a tagged release carries the APK and the phone runs it.
  8. Retire the other control app on the phone (the owner), once A4 passes.

Emulator and phone

Phone checkpoints

Run on the phone with debug on, and paste the in-app log.

Decisions

Not planned