v0.19.0: andTunes plays music on the Rabbit
The Android half of andTunes had been paused on a 1 GB toolchain download (Gradle wrapper, Android Gradle Plugin, Kotlin). None of that was needed: platform 33 and build-tools 34 were already in ~/Android/Sdk, and an app that links no libraries needs five SDK steps, not a build system. andtunes/build.py runs aapt2 -> javac -> R8 -> zipalign -> apksigner and produces a 61 KiB APK. andTunes 0.1.0 is framework-only Java: a six-tile menu that loads nothing, a streaming library.json parse grouped into artists and albums in one pass, one ListActivity for every list (songs and artist songs open with Shuffle all, albums carry art thumbs, search as you type), a Now playing bar under every list, and a white Now Playing screen with square art, a slim scrubber, prev/play/next, shuffle and repeat, and no back button. A foreground PlaybackService owns the queue: audio focus, becoming-noisy, MediaSession and a MediaStyle notification, end of queue pauses, missing files skip, and the last queue and position come back on launch. Black on white, and every size doubled for the R1's override density. Cold start to the menu: 313-338 ms. The scroll wheel turned out to be DPAD_UP/DOWN (stock Generic.kl), not volume. The app takes both in dispatchKeyEvent: lists scroll a row per detent, and the menu and Now Playing get volume. LinTunes side: Connections > Install andTunes on Rabbit... installs the committed APK over adb and grants all-files access, or copies it to Download/ over MTP when there's no adb. Sync fills Buttons/ with default artwork only where a file is missing, so trav's own art is never overwritten. Verified against the dev library only, synced to the R1 and driven with adb input + screencap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSsYtRn4WZx6xj4GEdVPt9
This commit is contained in:
+39
-34
@@ -15,10 +15,12 @@ track number of every file it just copied, so it writes them into
|
||||
`Music/andTunes/library.json` at sync time and the app parses one file
|
||||
instead of indexing a filesystem. That is the entire architectural bet.
|
||||
|
||||
**Status (2026-09-10): the desktop half is done and shipped** in LinTunes
|
||||
v0.17.0 (`lintunes/andtunes/`, round 47). The Android app has not been
|
||||
started. Work is paused on a slow connection — see *Toolchain* below for the
|
||||
one blocking download.
|
||||
**Status (2026-09-11): andTunes 0.1.0 plays music on the Rabbit.** The
|
||||
desktop half shipped in LinTunes v0.17.0 (round 47); the app itself landed in
|
||||
round 50 (LinTunes v0.19.0), built in plain Java with no Gradle and no
|
||||
download — see *Toolchain*. Cold start to the menu measures 313–338 ms on the
|
||||
R1 (target < 400). Play counts coming back and the format gate are next
|
||||
(`TASKS.md`, phase 3).
|
||||
|
||||
---
|
||||
|
||||
@@ -89,49 +91,51 @@ screen where the stock behaviour is the useful one.
|
||||
|
||||
## Toolchain
|
||||
|
||||
Already done on this machine:
|
||||
Everything is already on this machine, and **nothing else gets downloaded**:
|
||||
|
||||
- `java-21-openjdk-devel` — installed, `javac` on PATH. (Java 25 is also
|
||||
installed; AGP won't run on it, so the Gradle build must be pointed at 21.)
|
||||
- `java-21-openjdk-devel` — `build.py` uses `/usr/lib/jvm/java-21-openjdk`
|
||||
(Java 25 is the system default; R8 and apksigner want 21).
|
||||
- `~/Android/Sdk/platforms/android-33` + `build-tools/34.0.0` (aapt2, d8/R8,
|
||||
zipalign, apksigner). No `cmdline-tools` / `sdkmanager` needed.
|
||||
- `adb` / `fastboot` — in `/usr/local/sbin`, platform-tools 31.0.3.
|
||||
|
||||
**The one thing left, and the reason this is paused: the Android SDK is a
|
||||
~1–1.5 GB download** and trav is on a very weak connection.
|
||||
**No Gradle and no Kotlin** (decided 2026-09-11). The original plan was
|
||||
Kotlin + Gradle, which is another ~1 GB of wrapper, Android Gradle Plugin and
|
||||
Kotlin compiler — tools that produce the same APK. An app that links no
|
||||
libraries needs exactly five SDK steps, so `build.py` runs them directly:
|
||||
aapt2 compile/link → javac (`--release 11`, against `android.jar`) → R8
|
||||
(`--release`, keep rules from aapt2) → zipalign → apksigner. Plain Java also
|
||||
means no Kotlin runtime inside the APK: it's 61 KiB. The source tree is the
|
||||
standard Gradle shape, so Gradle could be dropped in later with no moves.
|
||||
|
||||
```sh
|
||||
# 1. commandline-tools (~150 MB) from developer.android.com/studio
|
||||
# (bottom of the page, "Command line tools only", linux zip)
|
||||
mkdir -p ~/Android/Sdk/cmdline-tools
|
||||
unzip commandlinetools-linux-*_latest.zip -d ~/Android/Sdk/cmdline-tools
|
||||
mv ~/Android/Sdk/cmdline-tools/cmdline-tools ~/Android/Sdk/cmdline-tools/latest
|
||||
To build on a second machine: install `java-21-openjdk-devel`, and copy
|
||||
`~/Android/Sdk/platforms/android-33` and `build-tools/34.0.0` across (≈ 280 MB,
|
||||
no internet needed). `ANDROID_HOME` / `JAVA_HOME` override the default paths.
|
||||
|
||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
`andtunes.keystore` is committed on purpose: both machines must sign the same
|
||||
way, or `adb install -r` refuses an update built on the other one. A sideloaded
|
||||
app with no store listing has nothing for the key to protect.
|
||||
|
||||
# 2. the platform we build against (~150 MB total)
|
||||
sdkmanager --licenses
|
||||
sdkmanager "platforms;android-33" "build-tools;34.0.0"
|
||||
```
|
||||
|
||||
Then the **first** Gradle build pulls the wrapper (~130 MB) and the Android
|
||||
Gradle Plugin's dependencies (several hundred MB). After that it's all cached
|
||||
and `./gradlew --offline assembleDebug` works with no connection at all — so
|
||||
the download is a one-time cost, but it is not a small one. Do it on a decent
|
||||
connection.
|
||||
R8 gotcha: `-keepattributes SourceFile,LineNumberTable` makes R8 8.2 die with
|
||||
an internal NPE on javac 21's debug info for an anonymous class, so
|
||||
`proguard.pro` keeps none. Stack traces still carry class and method names
|
||||
(`-dontobfuscate`).
|
||||
|
||||
Fedora note: don't `dnf install android-tools` over the existing
|
||||
`/usr/local/sbin/adb`; two adb versions on PATH fight over the server port.
|
||||
|
||||
---
|
||||
|
||||
## Building and installing (once the SDK is there)
|
||||
## Building and installing
|
||||
|
||||
```sh
|
||||
cd andtunes
|
||||
./gradlew assembleDebug
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n me.teafry.andtunes/.MenuActivity
|
||||
python3 andtunes/build.py # -> andtunes/build/andTunes.apk
|
||||
python3 andtunes/build.py --install # ... + adb install -r and launch
|
||||
python3 andtunes/build.py --ship # ... + copy into lintunes/android/
|
||||
|
||||
# after a fresh install by hand, the grants LinTunes' installer does for you:
|
||||
adb shell appops set --uid me.teafry.andtunes MANAGE_EXTERNAL_STORAGE allow
|
||||
adb shell pm grant me.teafry.andtunes android.permission.POST_NOTIFICATIONS
|
||||
|
||||
# the number the whole project is judged on:
|
||||
adb shell am start -W -n me.teafry.andtunes/.MenuActivity | grep TotalTime
|
||||
@@ -143,7 +147,8 @@ Target: menu visible in **under 400 ms**.
|
||||
|
||||
## Design decisions already locked
|
||||
|
||||
- **Kotlin, no Compose, no androidx, no Media3.** Framework `Activity`,
|
||||
- **Java, no Compose, no androidx, no Media3** (Kotlin was the first plan;
|
||||
see *Toolchain* for why it went). Framework `Activity`,
|
||||
`ListView`, `MediaPlayer`, `MediaSession`, `Notification.MediaStyle`, R8
|
||||
minify. Cold start is the feature; there is nothing to initialize if nothing
|
||||
is linked in. Same instinct as `trash.py` and `sync_identity.py` on the
|
||||
|
||||
Reference in New Issue
Block a user