Course → Android APK
Wraps a finished book-to-course course into a small Android app: one Activity with a WebView that serves the course
files from inside the APK. No Gradle, no Android Studio project, no network permission — the build calls the SDK tools
directly (aapt2 → javac → d8 → zipalign → apksigner), so the APK is roughly the size of the course plus ~10 KB.
The app wrapper (assets/android/) and the scripts are fixed and tested — your job is the conversation around them:
find the course, get the toolchain in place with the user's consent, build, and explain how to put it on the device.
script (in scripts/) | purpose |
|---|---|
toolchain.py [--json] | what is installed, what is missing, where to download it; exit 0 = ready |
setup_toolchain.py --dry-run | list exactly what would be downloaded (name, size, URL) — downloads nothing |
setup_toolchain.py --show-license | print the Android SDK licence text |
setup_toolchain.py --accept-licenses | download only the missing parts into ~/.local/share/course-to-apk (no sudo) |
build_apk.py COURSE_DIR [--install] | build the signed APK into <COURSE_DIR>-android/ |
All scripts are Python 3 standard library only.
Workflow
1. Find the course
A course folder has index.html, assets/app.js and course/data.js. Look where the user points, in the current
directory, or among recent book-to-course output. If they give a .zip (from pack_course.py), unzip it next to the zip
first — it contains one top folder named after the course; that folder is COURSE_DIR. If content/ exists but course/data.js is missing or older than the lessons, the course was not rebuilt —
run book-to-course's build_course.py COURSE_DIR first (when that skill is available), otherwise ask the user to.
2. Check the toolchain
bashpython3 scripts/toolchain.py
It searches well beyond PATH (JAVA_HOME, ANDROID_HOME, ~/Android/Sdk, ~/.jdks, Android Studio's bundled JBR,
/usr/lib/jvm, …), so a JDK that which java does not see is still found. If it prints READY, go to step 4.
3. Something is missing → ask, don't assume
Tell the user, in their language, what is missing — use the report's lines, they already contain the official download
links and package-manager commands for their OS. Then run setup_toolchain.py --dry-run so you can quote the real sizes
and URLs, and ask them to choose (AskUserQuestion works well here):
- "I'll install it myself" — give the list again as a short checklist (what, where from, and that a non-standard
location needs
JAVA_HOME/ANDROID_HOME). Then stop and wait. Do not poll, do not build, do not download anything. When they write that it's installed, reruntoolchain.py; if something is still missing, say exactly what and wait again. - "Download it for me" — the Android SDK packages are under the Android SDK License Agreement
(https://developer.android.com/studio/terms; full text via
--show-license). Their choice of this option must include agreeing to it — say so in the question. Only then runpython3 scripts/setup_toolchain.py --accept-licenses(≈320 MB if the JDK is also missing, ≈125 MB otherwise; it verifies checksums and installs only into~/.local/share/course-to-apk, nothing system-wide, no sudo).
Why the care: downloading hundreds of megabytes and accepting a licence are the user's decisions, not yours, and a user who said they'd install things themselves is mid-installation — building or downloading behind their back collides with that.
4. Build
bashpython3 scripts/build_apk.py "<COURSE_DIR>"
Defaults that are right for almost everyone — mention them, don't ask:
- Output
<COURSE_DIR>-android/<course-id>.apk, next to the course folder;apk.jsonthere remembers the package id, label, icon colour and an auto-incremented versionCode. - Signing with the Android debug key
~/.android/debug.keystore(the standard developer certificate; created with keytool if it doesn't exist). Fine for installing on your own devices; it is not a Play Store release key. If the user has their own keystore:--keystore FILE --key-alias A --ks-pass P(orAPK_KS_PASSenv var so the password doesn't land in shell history); it's remembered inapk.json. - Package id
net.booktocourse.<course_id>, label = course title. Override with--package/--labelonly when asked — changing the package later makes Android treat it as a different app and the learner loses progress. - Android 7.0+ (minSdk 24); targetSdk = the newest platform installed.
If the build fails, the error names the tool and shows its output; see references/troubleshooting.md.
5. Hand it over
Tell the user in a few lines: where the APK is (absolute path), its size, and how to install it:
- Copy to the device (USB cable, cloud drive, e-mail to yourself), open the file on the device, allow "install unknown apps" for the app that opens it when Android asks; Play Protect may warn about an unknown developer → "Install anyway".
- Or over USB with adb: enable Developer options → USB debugging, connect, then
build_apk.py … --install(oradb install -r <apk>). Offer this iftoolchain.pyshowed adb and a device is connected (adb devices).
Also say, briefly:
- Progress is stored inside the app on the device (not in
progress/progress.jsonof the computer copy) and survives updates — rebuild with the same folder and key after adding chapters and install over the old app. Uninstalling deletes it. - Code exercises: the "Run tests" button needs the computer version (
start.sh/start.bat); on the phone the page shows the test command instead. Lessons, quizzes, tests and flashcards all work offline.
What the app does (so you can answer questions)
- Serves the course from
https://appassets.androidplatform.net/viashouldInterceptRequest— a reserved host that never hits the network, giving the page a stable origin forlocalStorage. No INTERNET permission; links to other sites open in the user's browser. - Back button/gesture goes back through the course's history, then closes the app; edge-to-edge insets (status bar, cutouts, keyboard) are handled; dark/light follows the system on first launch, then the course's own theme toggle.
alert()/confirm()show real dialogs (the player avoids them anyway).
To change the wrapper itself (e.g. lock orientation), edit assets/android/ — MainActivity.java, AndroidManifest.xml
(placeholders {{PACKAGE}}, {{LABEL}}), res/. Keep it framework-only: there is no Gradle, so AndroidX libraries can't be added.


