153 lines
6.0 KiB
Markdown
153 lines
6.0 KiB
Markdown
# BAL Reader (Android)
|
|
|
|
A minimal Android app that reads a Bitcoin will exported by the
|
|
[BAL Electrum plugin](https://bitcoin-after.life) directly from your screen,
|
|
then lets you view, copy, share, or save the recovered data. **Reader only** —
|
|
it never signs or broadcasts.
|
|
|
|
It decodes **all four** transfer formats the plugin can export, auto-detecting
|
|
the format from the first frame:
|
|
|
|
- **BAL QR** (the plugin's default), single- and multi-frame, plain and
|
|
zlib-compressed, compact `BAL1` header (legacy `BALQR1` frames are still
|
|
accepted on import);
|
|
- **BC-UR v1** (single-part and `NofM` multipart);
|
|
- **BC-UR v2** (single-part and XOR-fountain multipart — it tolerates dropped,
|
|
repeated, and out-of-order frames);
|
|
- **BBQR** (`Z`/`H`/`2` encodings).
|
|
|
|
Both payload kinds are handled: the **whole-will JSON** and the plain
|
|
**transaction-hex list**.
|
|
|
|
## How decoding works
|
|
|
|
The app does not reimplement the QR formats. It bundles the plugin's own
|
|
codec modules — `bal/core/__init__.py`, `bal/core/animated_qr.py`,
|
|
`bal/core/qrtransfer.py` — and runs them verbatim through **Chaquopy** (CPython
|
|
on Android). Kotlin is only camera glue and UI:
|
|
|
|
```
|
|
Camera (CameraX) → ML Kit QR detection (on-device, no API key)
|
|
→ BalDecoder.add(text) → bal.core.animated_qr.AnimatedQrSession
|
|
→ when done → BalDecoder.finish()
|
|
= session.resolve() → qrtransfer.decode_transfer()
|
|
→ balreader.payload.decode_will_payload()
|
|
→ ResultActivity: view / copy / share / save
|
|
```
|
|
|
|
The decode tail mirrors the plugin's import dialog function-for-function, and
|
|
`android/test_chain/verify_chain.py` proves the bundled code decodes every
|
|
format the way the desktop import does (including scrambled, duplicated, and
|
|
missing frames).
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
android/
|
|
├── app/src/main/
|
|
│ ├── AndroidManifest.xml
|
|
│ ├── java/life/after/bitcoin/
|
|
│ │ ├── BalDecoder.kt Chaquopy bridge over the bundled codecs
|
|
│ │ ├── MainActivity.kt camera + ML Kit scan loop + progress
|
|
│ │ └── ResultActivity.kt viewer (copy / share / save)
|
|
│ └── python/ bundled Python (regenerate, do not hand-edit)
|
|
│ ├── bal/core/ SYNCED COPY of the plugin codecs
|
|
│ └── balreader/payload.py verbatim copy of dialogs.decode_will_payload
|
|
├── scripts/
|
|
│ ├── sync_codecs.py re-copy + verify the bundled codecs
|
|
│ └── build_apk.py resync codecs, run Gradle, print APK + sha256
|
|
└── test_chain/verify_chain.py decode-chain simulation for all formats
|
|
```
|
|
|
|
## Build
|
|
|
|
You need Android Studio (Jellyfish or newer), JDK 17, an Android SDK with
|
|
platform 35, and a network connection for the first Gradle sync.
|
|
|
|
1. Open this `android/` folder in Android Studio and let it sync (it will
|
|
fetch the Gradle wrapper 8.14, AGP 8.10.0, Kotlin 2.0.21, Chaquopy 17.0.0,
|
|
CameraX 1.3.4, and ML Kit).
|
|
2. Connect a phone (API 24+) or start an emulator and press **Run**.
|
|
3. Grant the camera permission when asked.
|
|
|
|
Alternatively, from the command line (from the repository root):
|
|
|
|
```bash
|
|
python3 android/scripts/build_apk.py # debug APK + sha256
|
|
python3 android/scripts/build_apk.py --release # (unsigned) release APK
|
|
```
|
|
|
|
The script re-synchronises the bundled codec modules first (so the APK always
|
|
carries the current `bal/core` sources), runs `./gradlew`, and prints the APK
|
|
path, size and sha256. Flags: `--no-sync` (skip the re-sync), `--offline`
|
|
(Gradle without downloads), `--clean`, `--verbose`.
|
|
|
|
Equivalent raw Gradle call:
|
|
|
|
```bash
|
|
cd android
|
|
./gradlew assembleDebug # APK: android/app/build/outputs/apk/debug/app-debug.apk
|
|
```
|
|
|
|
### If the Gradle wrapper jar is missing
|
|
|
|
`gradle/wrapper/gradle-wrapper.jar` is committed so `./gradlew` works out of
|
|
the box. If it is ever absent, Android Studio regenerates it on the first
|
|
sync; no manual steps needed.
|
|
|
|
## Use
|
|
|
|
1. In Electrum + BAL, open the will's **export** dialog.
|
|
2. Pick a format — start with the default **BAL QR**, then try **BC-UR v1**,
|
|
**BC-UR v2**, and **BBQR**.
|
|
3. Make sure the wording toggle shows a payload (business logic), then display
|
|
the animated QR and keep it on screen.
|
|
4. Point the phone at the screen. The header shows the detected format and
|
|
`received / total`; scanning stops automatically when the transfer is
|
|
complete.
|
|
5. On the result screen: **Copy** the raw transfer, **Share** it, **Save** it
|
|
as `will.json` (whole will) or `will_tx.txt` (transaction list), or press
|
|
**Scan another**.
|
|
|
|
Notes:
|
|
|
|
- Keep the phone still and the whole QR inside the frame (the codec dedups
|
|
repeated frames, so a slow capture is fine).
|
|
- If the camera glares off the screen, reduce brightness or tilt slightly.
|
|
- If scanning jumps between exports, the app detects the format switch,
|
|
resets, and asks you to let it re-scan.
|
|
|
|
## Keeping the bundled code in sync with the plugin
|
|
|
|
The codecs under `app/src/main/python/bal/core/` are **committed copies** for
|
|
deterministic builds, but they must stay identical to the plugin. Re-run this
|
|
after changing `bal/core/animated_qr.py` or `bal/core/qrtransfer.py` (and
|
|
after any change to `decode_will_payload` in `bal/gui/qt/dialogs.py`, which
|
|
mirrors `balreader/payload.py`):
|
|
|
|
```bash
|
|
python3 android/scripts/sync_codecs.py # copy
|
|
python3 android/scripts/sync_codecs.py --check # verify only (CI-friendly)
|
|
python3 android/test_chain/verify_chain.py # full decode-chain regression
|
|
```
|
|
|
|
`verify_chain.py` fails if the app's `balreader/payload.py` ever drifts from
|
|
the plugin's `decode_will_payload` (AST identity + result parity).
|
|
|
|
## Version pins (see `PLAN_ANDROID_READER.md`)
|
|
|
|
| Item | Version |
|
|
|---|---|
|
|
| AGP | 8.10.0 |
|
|
| Gradle | 8.14 (wrapper) |
|
|
| Kotlin | 2.0.21 |
|
|
| Chaquopy | 17.0.0 (Python 3.12) |
|
|
| compile / target / min SDK | 35 / 35 / 24 |
|
|
| CameraX | 1.3.4 |
|
|
| ML Kit barcode-scanning | 17.3.0 |
|
|
| JDK | 17 |
|
|
|
|
## License
|
|
|
|
MIT. The bundled Python codec files inherit the plugin's MIT license
|
|
(`bal/LICENSE`); see `app/src/main/python/bal/` for attribution. |