diff --git a/doc/README.md b/doc/README.md index cc1cb71..8ef478b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -38,21 +38,23 @@ line-by-line code — so they stay true as the implementation evolves. Prereqs on this box (already installed): JDK 17, Go, Android SDK at `~/android-sdk` (platform-tools, build-tools 35.0.0, emulator, NDK), -`gogio@v0.10.0` in `~/go/bin`, `apktool` at `/tmp/apktool.jar`, debug -keystore at `~/.android/debug.keystore`. Env vars are set in `~/.bashrc`. +`gogio@v0.10.0` in `~/go/bin`, debug keystore at +`~/.android/debug.keystore`. Env vars are set in `~/.bashrc`. -The full recipe is `/tmp/build_pad.sh` (machine-local). Steps: +**Build + install with `./scripts/build_emu.sh`** (add `--no-install` to +build only). It is self-contained: checks the prereqs, auto-downloads +apktool v3.0.3 to `~/android-sdk/tools/apktool.jar` if missing, and uses a +temporary work dir. Output: `cmd/pad/pad-emu.apk`. -1. `gogio -target android -targetsdk 35 -arch amd64 -o /tmp/pad-raw.apk .` - (from `cmd/pad/`) -2. `apktool d /tmp/pad-raw.apk -o /tmp/pad_decoded -f` +What it does, and why (gogio cannot inject manifest permissions): + +1. `gogio -target android -targetsdk 35 -arch amd64` → raw APK (from `cmd/pad/`) +2. `apktool d` → decode 3. Inject `` - into the decoded `AndroidManifest.xml` (Android 15 needs it for - `/storage/emulated/0`). -4. `apktool b /tmp/pad_decoded -o /tmp/pad-unsigned.apk` -5. `apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android - --key-pass pass:android --ks-key-alias androiddebugkey --out cmd/pad/pad-emu.apk /tmp/pad-unsigned.apk` -6. `adb install -r cmd/pad/pad-emu.apk` + (without it, Android 11+ blocks listing `/storage/emulated/0`) +4. `apktool b` → rebuild +5. `apksigner sign` with the debug key → `cmd/pad/pad-emu.apk` +6. `adb install -r` (skipped with `--no-install`) Run the emulator (headless, AVD `pad_avd`, API 35): diff --git a/doc/development_plan.md b/doc/development_plan.md index 398317f..df26076 100644 --- a/doc/development_plan.md +++ b/doc/development_plan.md @@ -153,7 +153,7 @@ API 35 `google_apis/x86_64` system image installed to `~/android-sdk`; AVD `pad_avd` (Pixel 6 profile) boots with KVM (`/dev/kvm` chmod 666). Build recipe: `gogio -target android -targetsdk 35 -arch amd64` → inject `MANAGE_EXTERNAL_STORAGE` via apktool → sign with the debug keystore → -`adb install -r` (scripted in `/tmp/build_pad.sh`). +`adb install -r` (scripted in `scripts/build_emu.sh`). **On-device IME validation — PASSING** (Gboard, API 35; observed via the gated `imeDebugLog` trace + logcat + autosaved file diff + screenshots): @@ -248,8 +248,8 @@ but are not needed for a usable v1. rules; over-detailed companion docs deleted — see `doc/README.md` policy). - ✓ Amend `spec.md` per §7 (2026-08-16: rewritten; unbuilt features moved to an explicit “deferred” table; see the corrections noted under §7 below). -- ✓ Build/install recipe (2026-08-16: in `doc/README.md`; machine-local script - `/tmp/build_pad.sh`). +- ✓ Build/install recipe (2026-08-16: in `doc/README.md`; self-contained + in-repo script `scripts/build_emu.sh`). - ☐ In-repo device test checklist (the adb tap/swipe/IME sequences used in Phases 2–3 and Phase 6 are in this plan's phase notes but not a standalone checklist). diff --git a/scripts/build_emu.sh b/scripts/build_emu.sh new file mode 100755 index 0000000..88571f0 --- /dev/null +++ b/scripts/build_emu.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Build (and optionally install) the Pad Android APK for the local emulator. +# +# Usage: +# ./scripts/build_emu.sh # build + install to the connected device +# ./scripts/build_emu.sh --no-install # build only +# +# Output: cmd/pad/pad-emu.apk +# +# Prereqs on this workstation (the script checks each and explains what is +# missing): +# - JDK 17 (java on PATH) +# - Go + gogio v0.10.0 (~/go/bin) +# - Android SDK at ~/android-sdk (platform-tools, build-tools 35.0.0) +# - debug keystore at ~/.android/debug.keystore +# apktool is not a prerequisite: if it is missing, the script downloads +# v3.0.3 to the stable location ~/android-sdk/tools/apktool.jar. +# +# Why the apktool detour: gogio does not offer a way to add +# MANAGE_EXTERNAL_STORAGE to the manifest, and Android 11+ blocks the app's +# directory listing without it. So we decode the gogio APK, inject the +# permission, rebuild, and re-sign with the debug key. +set -euo pipefail + +REPO=$(cd "$(dirname "$0")/.." && pwd) +export ANDROID_HOME=${ANDROID_HOME:-$HOME/android-sdk} +export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/35.0.0:$HOME/go/bin + +INSTALL=1 +[ "${1:-}" = "--no-install" ] && INSTALL=0 + +die() { echo "ERROR: $*" >&2; exit 1; } + +command -v java >/dev/null 2>&1 || die "java not found (need JDK 17)" +command -v gogio >/dev/null 2>&1 || die "gogio not found (need v0.10.0 in ~/go/bin)" +command -v apksigner >/dev/null 2>&1 || die "apksigner not found (need $ANDROID_HOME/build-tools/35.0.0)" +command -v adb >/dev/null 2>&1 || die "adb not found (need $ANDROID_HOME/platform-tools)" +[ -f "$HOME/.android/debug.keystore" ] || die "debug keystore missing: $HOME/.android/debug.keystore" + +# apktool: auto-download to a stable location if missing (never /tmp, which +# gets wiped between sessions). +APKTOOL="$ANDROID_HOME/tools/apktool.jar" +if [ ! -f "$APKTOOL" ]; then + echo "=== apktool missing; downloading v3.0.3 to $APKTOOL ===" + mkdir -p "$ANDROID_HOME/tools" + curl -fsSL -o "$APKTOOL" \ + https://github.com/iBotPeaches/Apktool/releases/download/v3.0.3/apktool_3.0.3.jar \ + || die "apktool download failed" +fi + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +echo "=== gogio (Go -> APK) ===" +(cd "$REPO/cmd/pad" && gogio -target android -targetsdk 35 -arch amd64 -o "$WORK/pad-raw.apk" .) + +echo "=== apktool decode ===" +java -jar "$APKTOOL" d "$WORK/pad-raw.apk" -o "$WORK/decoded" -f >/dev/null + +echo "=== inject MANAGE_EXTERNAL_STORAGE ===" +grep -q "MANAGE_EXTERNAL_STORAGE" "$WORK/decoded/AndroidManifest.xml" || \ + sed -i '0,/\n /dev/null + +echo "=== sign ===" +apksigner sign \ + --ks "$HOME/.android/debug.keystore" --ks-pass pass:android \ + --key-pass pass:android --ks-key-alias androiddebugkey \ + --out "$REPO/cmd/pad/pad-emu.apk" "$WORK/pad-unsigned.apk" + +if [ "$INSTALL" = "1" ]; then + echo "=== install ===" + adb get-state >/dev/null 2>&1 || die "no adb device (start the emulator or plug in a phone)" + adb install -r "$REPO/cmd/pad/pad-emu.apk" +fi + +echo "=== DONE: $REPO/cmd/pad/pad-emu.apk ==="