Files
lintunes/andtunes/TASKS.md
T
travandClaude Opus 5 def7635a87 v0.20.3: one artist spelled two ways
The sync was fine. Everything it put on the Rabbit was there and correct --
2,432 media files, 208 covers, a manifest whose every field type-checks
against the parser -- and andTunes still answered "No library / Couldn't read
library.json". Fifteen tracks took all 2,439 songs down with them.

Library.group() keyed albumsByKey case-folded and artistsByName raw-case, and
only ever built the Artist inside `if (album == null)`. So the second track of
an album whose artist is spelled differently found the album already there,
skipped the block that would have made the artist, and dereferenced the null
that came back. Six artists in trav's library are spelled two ways: RJD2/Rjd2,
Toro Y Moi/Toro y Moi, FatBoy Slim/Fatboy Slim, LOVING/Loving, Land Of The
Loops/Land of the Loops, Salami Rose Joe Louis/salami rose joe louis. It had
worked until the 12th because that sync was the first to carry both spellings
of one of those albums.

Both maps fold case now, and the artist is fetched-or-made before the album
block and held, so no lookup left in group() can come back null. Artist gains
a key the way Album always had one, and ListActivity navigates by it --
ALBUM_ROW already passed album.key, so artists just stopped being the
exception.

The device filesystem is case-insensitive, so those two spellings are one
folder there. plan_andtunes_sync compared exact strings and saw every such
file as stale *and* missing, deleting and re-copying it over MTP on every
sync forever; the diff and the collision rule both fold now. plan.stale still
carries the device's own spelling, since that is what _delete_stale unlinks by.

Verified on the Rabbit: 2,439 songs listed, RJD2 one row of 12 songs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKXUgsBBwe3qaHEjeV8ubP
2026-09-14 12:51:40 -07:00

111 lines
6.4 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: phases 1–3 done (2026-09-11, LinTunes rounds 50–51, andTunes
0.2.0); 0.2.1 (LinTunes round 54, 2026-09-14) fixed the grouping crash —
`Library.group()` keyed albums case-folded but artists raw-case, so one artist
spelled two ways on one album ("RJD2" / "Rjd2") took the whole library down
with `Couldn't read library.json`. Both maps fold case now, and `Artist` has a
`key` the way `Album` always did.** What's left is the *Parked* list, pulled in
as trav finds he needs it. Built with
`python3 andtunes/build.py` — plain Java, no Gradle, nothing downloaded (see
`README.md`, *Toolchain*). 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**,
mapped by the stock `Generic.kl` (checked 2026-09-11 via `dumpsys input`)
to **`KEYCODE_DPAD_UP` / `DPAD_DOWN`** — not volume. Side/PTT button sits
on `mtk-kpd` with KEY_VOLUMEDOWN + KEY_POWER. Headset exposes
KEY_PLAYPAUSE, so a MediaSession gets media keys for free.
- USB: set to come up as **`mtp,adb`** whenever the screen is unlocked
(`adb shell svc usb setScreenUnlockedFunctions mtp`, 2026-09-11), so the
gvfs mount sync uses and the adb install uses are both there at once.
## Phase 1 — plays a song (v0.1.0) ✔
- [x] `java-21-openjdk-devel` — installed, `javac` on PATH. (Java 25 is also
present; `build.py` points at 21.)
- [x] Android SDK: platform 33 + build-tools 34.0.0 were already in
`~/Android/Sdk`. **No Gradle, no Kotlin** — `build.py` runs aapt2 →
javac → R8 → zipalign → apksigner directly (trav, 2026-09-11: don't
pull a GB over cell if it isn't needed; it isn't).
- [x] Project, package `me.teafry.andtunes`, minSdk 26 / targetSdk 33,
framework only: `Activity`, `ListView`, `MediaPlayer`, `MediaSession`,
`Notification.MediaStyle`, R8. The APK is 61 KiB.
- [x] `MANAGE_EXTERNAL_STORAGE` + a first-run screen that sends the user to
the toggle (LinTunes' installer grants it over adb, so it rarely shows).
- [x] Library loader: `android.util.JsonReader` streaming parse off the main
thread into a process singleton; artists/albums grouped in one pass;
an unknown `format` gets a readable message; reloads when
`library.json` changes (a re-sync) the next time the menu resumes.
- [x] `MenuActivity`: six tiles, 2 × 3, **loads nothing**. The sixth reads
"Now Playing" or "Shuffle All" depending on the service.
- [x] `ListActivity` in songs mode + "Shuffle all" header row.
- [x] `PlaybackService` (foreground) + `NowPlayingActivity`: white
background, square art, slim scrubber, ◀ ⏯ ▶, shuffle, repeat.
**No back button.**
- [x] Audio focus, ACTION_AUDIO_BECOMING_NOISY, MediaSession + notification.
- [x] End of queue **pauses** unless repeat is on. Repeat: off / all / one.
- [x] `adb shell am start -W`: **313–338 ms** cold to the menu (551 ms on the
very first launch after install, which includes dex verification).
## Phase 2 — the rest of the screens ✔
- [x] Playlists, artists (→ "All songs" + albums; all songs has its own
shuffle), albums, search (artists, albums and songs as you type).
- [x] Album art thumbs in album rows, full art on now playing.
- [x] The "Now playing" bar at the bottom of every list screen; the playing
song is drawn inverted in any list it appears in.
- [x] Scroll wheel → list scrolling (one row per detent), volume on the menu
and now-playing screens. Taken in `dispatchKeyEvent`, DPAD *and*
VOLUME codes, down *and* up, so the search box can't swallow it.
- [x] Replaceable button images: `Buttons/<name>.png` beats the bundled
drawable, for the six menu tiles plus ◀ ▶ ⏯. LinTunes ships the
defaults into `Buttons/` only where the file is missing.
- [x] Missing file (synced then deleted) skips with a toast, doesn't stop
the queue.
- [x] Resume last queue + track + position on launch (paused).
## Phase 3 — install from LinTunes, counts coming back
- [x] `Connections → Install andTunes on Rabbit…` (LinTunes 0.19.0):
`adb install -r` + the two permission grants; says Install / Update /
Reinstall from the version on the device; with no adb, copies the APK
to `Download/` over MTP for a manual tap. APK committed at
`lintunes/android/andTunes.apk` (+ `andTunes.json`, its version).
- [x] Format gate (LinTunes 0.20.0): sync reuses
`export/web_support.conversion_for` outright, because Android's
MediaPlayer decodes the same set a browser does. Protected AAC is
refused and reported; ALAC, AIFF and anything else unplayable becomes
**FLAC** (never a lossy re-encode). Conversions are cached per track, so
they're converted once and size-diffed after that.
- [x] Play counts back (andTunes 0.2.0): a natural finish counts a play, as
it does in LinTunes. The app keeps `plays/andtunes-<install id>.json` in
the per-machine-totals shape of `storage/play_journal.py`. Sync folds it
into `<data_dir>/plays/` with the per-track max *before* copying
anything, so the R1 is one more machine in the Round 38 model and there
are no new merge rules.
- [x] Retired `Sync Playlist to Rabbit (Auxio)` from the menu. (trav's R1
has no old `Music/<Playlist>/` folders, so no cleanup was needed.)
## 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.