Files
lintunes/andtunes/TASKS.md
T
travandClaude Opus 5 bcba621f3f v0.17.1: a library to break
Round 47 was verified by loading trav's real 21,531-track library. Nothing was
written and nothing was at risk, but it's the wrong habit and he said so. The
alternative is better for development anyway: reproducible, in-repo, and full
of the awkward cases on purpose rather than by luck.

scripts/make_dev_library.py builds a complete synthetic library — data dir and
music tree — from ffmpeg sine waves in six containers, with art on some albums
and not others, every playlist type (regular, folder, live/non-live/
unsupported/limited+nested smart, system, empty, duplicates), a play journal
and a tombstone. ~4 MB, gitignored; the generator is the artifact worth
keeping, not the sine waves.

The edge cases are the point. A collision pair identical in artist/album/title/
number, slashes and colons in every name, a track with no artist, a multi-disc
release, a compilation whose album_artist differs, a dangling location, a
208-character title, and non-ASCII plus an emoji all the way out to the m3u
filename. CLAUDE.md now carries the rule: never develop against the real
library, and when a feature needs a shape the fixture lacks, add it here.

Using it found two bugs in it — a relative --out made every location resolve
against the wrong root, and four-minute uncompressed clips made it 62 MB.

andTunes is paused on a weak connection (the Android SDK is a ~1-1.5 GB
one-time download; after that --offline builds need nothing). andtunes/
README.md now carries everything needed to resume cold: measured device facts,
the dp trap, exact toolchain commands, locked design decisions, and the
library.json contract. JDK 21 is installed and ticked off.

Also answers the scroll wheel question: it reads as volume because the ROM's
key layout maps the wheel's KEY_UP/KEY_DOWN to KEYCODE_VOLUME_UP/DOWN, but a
focused activity sees key events first — so andTunes can claim it by consuming
both those and DPAD_UP/DOWN, in onKeyDown and onKeyUp. No system file touched,
no other app affected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wrTys5U1fLb4pBD2LKWzV
2026-09-10 00:49:26 -04:00

101 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# andTunes — task board
A music player for the Rabbit R1 that never scans anything: LinTunes writes
`Music/andTunes/library.json` during sync and the app just reads it. See
`../andTunes spec.txt` for the original brief.
Legend: `[ ]` todo · `[~]` in progress · `[x]` done.
**Status: paused 2026-09-10** on a weak connection — the Android SDK is a
~1–1.5 GB download. Everything needed to resume cold is in `README.md`:
device facts, toolchain commands, locked design decisions, and the exact
shape of the `library.json` LinTunes already writes. Phases are andTunes'
own; which LinTunes round each lands in is decided when it starts.
## Device facts (measured 2026-09-06, `adb shell wm size` etc.)
- Panel **480 × 640 px**, physical density 320, **override density 160** — so
the app sees a 480 × 640 **dp** canvas on a 2.88" screen (~278 real ppi).
- **One dp is about half its usual physical size here.** Double every stock
value: body text 28–32 sp, list rows ≥ 88 dp, menu tiles ~213 dp. A 48 dp
row would be 4.4 mm tall. Do not "fix" this by changing the device density —
trav likes it where it is.
- Android 13 / API 33, `gsi_r1-userdebug`, arm64-v8a, 65 GB free.
- Scroll wheel = input device `och1970_holl_key` → **KEY_UP / KEY_DOWN**
(`KEYCODE_DPAD_UP` / `DPAD_DOWN`). Side/PTT button sits on `mtk-kpd` with
KEY_VOLUMEDOWN + KEY_POWER. Headset exposes KEY_PLAYPAUSE, so a MediaSession
gets media keys for free.
## Phase 1 — plays a song (v0.1.0)
- [x] `java-21-openjdk-devel` — installed, `javac` on PATH. (Java 25 is also
present; AGP won't run on it, so point the build at 21.)
- [ ] **Android SDK — the one blocker.** commandline-tools + platform 33 +
build-tools into `~/Android/Sdk`, then a first Gradle build to warm the
wrapper and AGP caches. ~1–1.5 GB all told; after that
`./gradlew --offline assembleDebug` needs no connection. Exact commands
in `README.md`.
- [ ] Gradle project, package `me.teafry.andtunes`, minSdk 26 / targetSdk 33.
**No Compose, no androidx, no Media3** — framework `Activity`,
`ListView`, `MediaPlayer`, `MediaSession`, `Notification.MediaStyle`,
R8 minify. Cold start is the whole feature; there is nothing to
initialize if nothing is linked in.
- [ ] `MANAGE_EXTERNAL_STORAGE` + a first-run screen that sends the user to
the toggle. `READ_MEDIA_AUDIO` grants the audio files but **not**
`library.json`, which isn't media — that's the whole reason.
- [ ] Library loader: parse `library.json` off the main thread into a
singleton that survives between activities. Refuse an unknown `format`
with a readable message rather than half-reading it.
- [ ] `MenuActivity`: six tiles, 2 × 3, **loads nothing**. The sixth reads
"Now Playing" or "Shuffle All" depending on the service.
- [ ] `ListActivity` in songs mode + "Shuffle all" header row.
- [ ] `PlaybackService` (foreground) + `NowPlayingActivity`: white
background, square art, slim scrubber, ◀ ⏯ ▶, shuffle. **No back
button** — Android's own is always on screen.
- [ ] Audio focus, ACTION_AUDIO_BECOMING_NOISY, MediaSession + notification.
- [ ] End of queue **pauses** unless repeat is on. Repeat: off / all / one.
- [ ] Verify with `adb shell am start -W` — target: menu visible < 400 ms.
## Phase 2 — the rest of the screens
- [ ] Playlists, artists (→ albums + "All songs" + shuffle for that artist),
albums, search.
- [ ] Album art thumbs in album rows, full art on now playing.
- [ ] The "Now playing" bar at the bottom of every list screen.
- [ ] Scroll wheel → list scrolling, and volume on the now-playing screen.
Consume `KEYCODE_VOLUME_UP/DOWN` **and** `DPAD_UP/DOWN` in both
`onKeyDown` and `onKeyUp` — the ROM's key layout decides which arrives,
and claiming them in a focused activity affects no other app. First
check `/system/usr/keylayout/` on the device; see `README.md`.
- [ ] Replaceable button images: load `Buttons/<name>.png` from the device
folder when present, else the bundled drawable. LinTunes ships defaults
into `Buttons/` only when the file is missing, so the user's own
artwork is never overwritten.
- [ ] Missing file (synced then deleted) skips with a toast, doesn't crash
the queue.
- [ ] Resume last track + position on launch.
## Phase 3 — install from LinTunes, counts coming back
- [ ] `Connections → Install andTunes on Rabbit…`: `shutil.which("adb")`
(the `ffmpeg_available()` runtime-tool pattern — never a pip dep), then
`adb install -r`; fall back to copying the APK over MTP for a manual
tap. APK committed at `lintunes/android/andTunes.apk` with
`package_data`, read via `importlib.resources` like `export/templates`.
- [ ] Format gate shaped like `export/web_support.py`: deny-by-default on
suffix, AIFF and protected AAC refused, anything else unplayable
converted to **FLAC** (never a lossy re-encode).
- [ ] Play counts back: the app writes `plays/andtunes-<installid>.json` in
the per-machine-totals shape of `storage/play_journal.py`; sync copies
it back and folds it in. The R1 becomes one more machine in the Round 38
model — no new merge rules.
- [ ] Retire `Sync Playlist to Rabbit (Auxio)` once andTunes covers it, and
offer a one-off cleanup of the old `Music/<Playlist>/` folders.
## Parked
- Gapless, crossfade, EQ, ratings, folder browsing, sleep timer, Android Auto.
- Deleting the old per-playlist folders automatically. They're outside the
andTunes root and the containment guard makes them unreachable on purpose;
removing them should be an explicit, separately-confirmed action.