# 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. 0.2.2 (round 55, 2026-09-14) reversed the wheel in lists and made it scroll smoothly (`WheelScroll` — a pixel debt paid off per frame, not a row per detent); volume on the other screens is unchanged.** 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/.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-.json` in the per-machine-totals shape of `storage/play_journal.py`. Sync folds it into `/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//` 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.