commit 4fe9eed8704b0a6bedacf7a86babe3e3eab97abd Author: xocko Date: Wed Oct 14 19:26:04 2026 +0200 Import miniabs 1.0 source Co-Authored-By: Claude Opus 5.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f17c59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +.DS_Store +._* diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..19a7888 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..898dab2 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# MiniABS + +A tiny Audiobookshelf **podcast** client for the Samsung Galaxy Mini (GT-S5570) and other Android 2.2+ phones. Native Java, no libraries, ~100 KB. + +## Install + +1. Copy `MiniABS.apk` to the phone (USB to the SD card, or download it in the browser). +2. On the phone: **Settings → Applications → Unknown sources** → tick it. +3. Open the APK from a file manager and install. It can be moved to the SD card (Settings → Applications → Manage applications → MiniABS → Move to SD card). + +## Connecting + +Use your server's **local http:// address**, e.g. `http://192.168.1.10:13378`. + +Android 2.x only speaks old TLS and has an outdated certificate list, so it usually can't connect to modern HTTPS servers (including Let's Encrypt behind a reverse proxy). The "Accept any HTTPS certificate" box only helps with self-signed certificates on servers that still allow older TLS. + +Sign in with your username and password, or tick "Use an API key instead". It works with both current servers (2.26+, short-lived access tokens that MiniABS refreshes automatically) and older ones (single long-lived token). + +## Using it + +- **Latest**: "Continue listening" plus the newest episodes across your podcast library. Tap the library name at the top to switch libraries. +- **Shows**: every podcast; tap one for its episodes. Menu key → sort oldest/newest, hide played. +- **Saved**: episodes downloaded to the SD card, plus download progress. +- **Tap** an episode to play. **Long-press** for Download, Delete download, Mark as played/unplayed, Go to show. +- **Player**: skip back/forward (set the amounts in Settings), seek bar, show notes. Menu key → sleep timer, mark as played, download, stop. +- **Headset buttons**: play/pause; next/previous skip forward/back. Playback pauses when headphones are unplugged and during calls. + +Colored dots: blue = new, amber = in progress (with a bar), grey = played. + +## Progress sync + +Playback opens an Audiobookshelf session and syncs every 15 seconds and on pause, so progress follows you to other apps. Saved episodes play offline; their positions are kept on the phone and sent to the server next time you're online (or tap refresh on the Saved tab). + +## Limits + +- Episodes are streamed with direct play. Formats Android 2.x can't decode (e.g. Opus) can't be played, since the transcoded HLS stream isn't supported on 2.x. +- No playback speed control: Android 2.x's media player doesn't support it. +- Downloads need an SD card. "Download on Wi-Fi only" is on by default. + +## Building + +Needs a JDK, `aapt`, `dalvik-exchange` (dx), `zipalign`, `apksigner` (Ubuntu: `apt install aapt dalvik-exchange zipalign apksigner`) and an API 8 `android.jar`: + + ANDROID_JAR=/path/to/android-8/android.jar ./build.sh + +Compiling against the API 8 jar guarantees no newer Android APIs are used. The APK is signed with `miniabs.keystore` (password `miniabs`); keep it to install updates over the top. + +`test/run_tests.sh` runs the API, JSON and progress code on the desktop against a mock Audiobookshelf server (`test/mock_abs.py`). diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c9364e2 --- /dev/null +++ b/build.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Builds MiniABS.apk for Android 2.2+ (API 8) using the plain SDK tools, no Gradle. +# ANDROID_JAR android.jar for API 8 (compiling against it guarantees no newer APIs sneak in) +# KEYSTORE signing keystore (created on first build if missing) +set -e +cd "$(dirname "$0")" +ANDROID_JAR=${ANDROID_JAR:-/home/claude/sable/android-platforms/android-8/android.jar} +KEYSTORE=${KEYSTORE:-miniabs.keystore} +OUT=build + +rm -rf "$OUT" +mkdir -p "$OUT/gen" "$OUT/classes" + +echo "== resources" +aapt package -f -m -J "$OUT/gen" -M AndroidManifest.xml -S res -I "$ANDROID_JAR" \ + -F "$OUT/MiniABS.unsigned.apk" + +echo "== compile (Java 8 bytecode, API 8 class library)" +javac -nowarn -Xlint:-options -encoding UTF-8 -source 8 -target 8 \ + -bootclasspath "$ANDROID_JAR" -classpath "$ANDROID_JAR" \ + -d "$OUT/classes" $(find src "$OUT/gen" -name '*.java') + +echo "== dex" +dalvik-exchange --dex --min-sdk-version=8 --output="$OUT/classes.dex" "$OUT/classes" +(cd "$OUT" && aapt add MiniABS.unsigned.apk classes.dex > /dev/null) + +echo "== align + sign (v1 JAR signature with SHA-1, which Android 2.x requires)" +if [ ! -f "$KEYSTORE" ]; then + keytool -genkeypair -keystore "$KEYSTORE" -storepass miniabs -keypass miniabs -alias miniabs \ + -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=MiniABS" > /dev/null 2>&1 +fi +zipalign -f 4 "$OUT/MiniABS.unsigned.apk" "$OUT/MiniABS.aligned.apk" +apksigner sign --ks "$KEYSTORE" --ks-pass pass:miniabs --key-pass pass:miniabs --ks-key-alias miniabs \ + --min-sdk-version 8 --v1-signing-enabled true --v2-signing-enabled false --v3-signing-enabled false \ + --out "$OUT/MiniABS.apk" "$OUT/MiniABS.aligned.apk" +apksigner verify --min-sdk-version 8 "$OUT/MiniABS.apk" +ls -l "$OUT/MiniABS.apk" diff --git a/miniabs.keystore b/miniabs.keystore new file mode 100644 index 0000000..787afaa Binary files /dev/null and b/miniabs.keystore differ diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..89a238c Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/ic_pause.png b/res/drawable-hdpi/ic_pause.png new file mode 100644 index 0000000..c647b71 Binary files /dev/null and b/res/drawable-hdpi/ic_pause.png differ diff --git a/res/drawable-hdpi/ic_pause_small.png b/res/drawable-hdpi/ic_pause_small.png new file mode 100644 index 0000000..5af5bc3 Binary files /dev/null and b/res/drawable-hdpi/ic_pause_small.png differ diff --git a/res/drawable-hdpi/ic_play.png b/res/drawable-hdpi/ic_play.png new file mode 100644 index 0000000..d06252b Binary files /dev/null and b/res/drawable-hdpi/ic_play.png differ diff --git a/res/drawable-hdpi/ic_play_small.png b/res/drawable-hdpi/ic_play_small.png new file mode 100644 index 0000000..dd67171 Binary files /dev/null and b/res/drawable-hdpi/ic_play_small.png differ diff --git a/res/drawable-hdpi/ic_refresh.png b/res/drawable-hdpi/ic_refresh.png new file mode 100644 index 0000000..c6f1b55 Binary files /dev/null and b/res/drawable-hdpi/ic_refresh.png differ diff --git a/res/drawable-hdpi/ic_stat.png b/res/drawable-hdpi/ic_stat.png new file mode 100644 index 0000000..573e526 Binary files /dev/null and b/res/drawable-hdpi/ic_stat.png differ diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..4e8f6d8 Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ diff --git a/res/drawable-ldpi/ic_pause.png b/res/drawable-ldpi/ic_pause.png new file mode 100644 index 0000000..88c8cbc Binary files /dev/null and b/res/drawable-ldpi/ic_pause.png differ diff --git a/res/drawable-ldpi/ic_pause_small.png b/res/drawable-ldpi/ic_pause_small.png new file mode 100644 index 0000000..080dfa6 Binary files /dev/null and b/res/drawable-ldpi/ic_pause_small.png differ diff --git a/res/drawable-ldpi/ic_play.png b/res/drawable-ldpi/ic_play.png new file mode 100644 index 0000000..e7a1ce7 Binary files /dev/null and b/res/drawable-ldpi/ic_play.png differ diff --git a/res/drawable-ldpi/ic_play_small.png b/res/drawable-ldpi/ic_play_small.png new file mode 100644 index 0000000..c337a11 Binary files /dev/null and b/res/drawable-ldpi/ic_play_small.png differ diff --git a/res/drawable-ldpi/ic_refresh.png b/res/drawable-ldpi/ic_refresh.png new file mode 100644 index 0000000..7c3724e Binary files /dev/null and b/res/drawable-ldpi/ic_refresh.png differ diff --git a/res/drawable-ldpi/ic_stat.png b/res/drawable-ldpi/ic_stat.png new file mode 100644 index 0000000..471322b Binary files /dev/null and b/res/drawable-ldpi/ic_stat.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..6a03758 Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_pause.png b/res/drawable-mdpi/ic_pause.png new file mode 100644 index 0000000..d34e668 Binary files /dev/null and b/res/drawable-mdpi/ic_pause.png differ diff --git a/res/drawable-mdpi/ic_pause_small.png b/res/drawable-mdpi/ic_pause_small.png new file mode 100644 index 0000000..af2ba37 Binary files /dev/null and b/res/drawable-mdpi/ic_pause_small.png differ diff --git a/res/drawable-mdpi/ic_play.png b/res/drawable-mdpi/ic_play.png new file mode 100644 index 0000000..dba13fe Binary files /dev/null and b/res/drawable-mdpi/ic_play.png differ diff --git a/res/drawable-mdpi/ic_play_small.png b/res/drawable-mdpi/ic_play_small.png new file mode 100644 index 0000000..2b4960b Binary files /dev/null and b/res/drawable-mdpi/ic_play_small.png differ diff --git a/res/drawable-mdpi/ic_refresh.png b/res/drawable-mdpi/ic_refresh.png new file mode 100644 index 0000000..dd2466f Binary files /dev/null and b/res/drawable-mdpi/ic_refresh.png differ diff --git a/res/drawable-mdpi/ic_stat.png b/res/drawable-mdpi/ic_stat.png new file mode 100644 index 0000000..e163c31 Binary files /dev/null and b/res/drawable-mdpi/ic_stat.png differ diff --git a/res/drawable/btn_accent.xml b/res/drawable/btn_accent.xml new file mode 100644 index 0000000..8c0de0d --- /dev/null +++ b/res/drawable/btn_accent.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/drawable/btn_flat.xml b/res/drawable/btn_flat.xml new file mode 100644 index 0000000..d44d996 --- /dev/null +++ b/res/drawable/btn_flat.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/btn_round.xml b/res/drawable/btn_round.xml new file mode 100644 index 0000000..dc294cf --- /dev/null +++ b/res/drawable/btn_round.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/drawable/row_sel.xml b/res/drawable/row_sel.xml new file mode 100644 index 0000000..68db390 --- /dev/null +++ b/res/drawable/row_sel.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/drawable/seek_thumb.xml b/res/drawable/seek_thumb.xml new file mode 100644 index 0000000..1995a85 --- /dev/null +++ b/res/drawable/seek_thumb.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/res/drawable/seek_track.xml b/res/drawable/seek_track.xml new file mode 100644 index 0000000..6eff8de --- /dev/null +++ b/res/drawable/seek_track.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/drawable/tab_bg.xml b/res/drawable/tab_bg.xml new file mode 100644 index 0000000..82f37c2 --- /dev/null +++ b/res/drawable/tab_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/drawable/thin_progress.xml b/res/drawable/thin_progress.xml new file mode 100644 index 0000000..d93f4b0 --- /dev/null +++ b/res/drawable/thin_progress.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/layout/activity_home.xml b/res/layout/activity_home.xml new file mode 100644 index 0000000..b288611 --- /dev/null +++ b/res/layout/activity_home.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/activity_login.xml b/res/layout/activity_login.xml new file mode 100644 index 0000000..ed0d3f2 --- /dev/null +++ b/res/layout/activity_login.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +