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
5.6 KiB
5.6 KiB
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: 0.1.0 plays music (2026-09-11, LinTunes round 50). 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 stockGeneric.kl(checked 2026-09-11 viadumpsys input) toKEYCODE_DPAD_UP/DPAD_DOWN— not volume. Side/PTT button sits onmtk-kpdwith KEY_VOLUMEDOWN + KEY_POWER. Headset exposes KEY_PLAYPAUSE, so a MediaSession gets media keys for free. - USB: set to come up as
mtp,adbwhenever 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) ✔
java-21-openjdk-devel— installed,javacon PATH. (Java 25 is also present;build.pypoints at 21.)- Android SDK: platform 33 + build-tools 34.0.0 were already in
~/Android/Sdk. No Gradle, no Kotlin —build.pyruns 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). - Project, package
me.teafry.andtunes, minSdk 26 / targetSdk 33, framework only:Activity,ListView,MediaPlayer,MediaSession,Notification.MediaStyle, R8. The APK is 61 KiB. MANAGE_EXTERNAL_STORAGE+ a first-run screen that sends the user to the toggle (LinTunes' installer grants it over adb, so it rarely shows).- Library loader:
android.util.JsonReaderstreaming parse off the main thread into a process singleton; artists/albums grouped in one pass; an unknownformatgets a readable message; reloads whenlibrary.jsonchanges (a re-sync) the next time the menu resumes. MenuActivity: six tiles, 2 × 3, loads nothing. The sixth reads "Now Playing" or "Shuffle All" depending on the service.ListActivityin songs mode + "Shuffle all" header row.PlaybackService(foreground) +NowPlayingActivity: white background, square art, slim scrubber, ◀ ⏯ ▶, shuffle, repeat. No back button.- Audio focus, ACTION_AUDIO_BECOMING_NOISY, MediaSession + notification.
- End of queue pauses unless repeat is on. Repeat: off / all / one.
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 ✔
- Playlists, artists (→ "All songs" + albums; all songs has its own shuffle), albums, search (artists, albums and songs as you type).
- Album art thumbs in album rows, full art on now playing.
- The "Now playing" bar at the bottom of every list screen; the playing song is drawn inverted in any list it appears in.
- 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. - Replaceable button images:
Buttons/<name>.pngbeats the bundled drawable, for the six menu tiles plus ◀ ▶ ⏯. LinTunes ships the defaults intoButtons/only where the file is missing. - Missing file (synced then deleted) skips with a toast, doesn't stop the queue.
- Resume last queue + track + position on launch (paused).
Phase 3 — install from LinTunes, counts coming back
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 toDownload/over MTP for a manual tap. APK committed atlintunes/android/andTunes.apk(+andTunes.json, its version).- Format gate shaped like
export/web_support.py: deny-by-default on suffix, protected AAC refused, anything else unplayable converted to FLAC (never a lossy re-encode). - Play counts back: the app writes
plays/andtunes-<installid>.jsonin the per-machine-totals shape ofstorage/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)now that andTunes covers it. (trav's R1 has no oldMusic/<Playlist>/folders, so no cleanup.)
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.