archy/Android/COMPANION_RELEASE.md
Dorian 07b9b5a3aa docs(android): companion release + App-Not-Installed runbook
Capture the 2026-06-26 lessons durably: ship via the hardened publish
script only, v1+v2+v3 signing is enforced by apksigner (AGP ignores
enableV1Signing at minSdk>=24), diagnose install failures with adb
install FIRST, signature-key changes force a one-time uninstall, and
keep all phone/adb work scoped to com.archipelago.app.debug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:21:48 +01:00

4.4 KiB

Companion App — Build, Ship & "App Not Installed" Runbook

Canonical procedure for releasing the Archipelago Companion Android app and for debugging install failures. Read this before touching the companion release flow. Hard lessons from 2026-06-26 are baked in below — don't relearn them.

Ship the companion (the only sanctioned way)

./Android/ship-companion.sh

This calls scripts/publish-companion-apk.sh (the single source of truth, also used by the .githooks/pre-push hook), which:

  1. Removes/rejects resource dirs whose names contain spaces. Empty stray mipmap-* NNN dirs (left by icon-export tools) break a clean build with Invalid resource directory name. Incremental builds hide them — clean builds don't.
  2. Always does a CLEAN build (:app:clean :app:assembleDebug).
  3. Forces v1 + v2 + v3 signing via zipalign + apksigner.
  4. Verifies all three schemes (apksigner verify --min-sdk-version 21) and aborts if any is missing.
  5. Stages the signed APK at neode-ui/public/packages/archipelago-companion.apk, commits, and pushes with SHIP_COMPANION=1 (the sanctioned pre-push bypass).

Never hand-roll gradlew assembleDebug + cp to the served path. That path skips the clean build and the signature enforcement and is exactly how a broken APK shipped.

Bump the version first

Edit Android/app/build.gradle.ktsversionCode (must strictly increase) and versionName. The committed value can drift AHEAD of what's actually built into the served APK, so verify the served APK's real version after shipping: aapt2 dump badging neode-ui/public/packages/archipelago-companion.apk | grep version.

Signing facts (important)

  • Debug builds are signed with the committed Android/app/debug.keystore (store/key pass android, alias androiddebugkey) so every machine and the served download share ONE signing key. Cert SHA-256: D6:22:E0:7E:…:66:4D.
  • AGP silently ignores enableV1Signing = true for minSdk ≥ 24, so a plain gradle build produces a v2-only APK. The apksigner step in the publish script is what actually guarantees v1+v2+v3 — do not remove it.
  • Changing the signing key forces every existing install to be uninstalled once. Android blocks in-place upgrades across different signatures. Treat the keystore as permanent; never regenerate it casually.

Debugging "App Not Installed" — DIAGNOSE FIRST

Do not theorize about signing schemes / OEM quirks. Get the real reason:

adb install ~/Desktop/archipelago-companion-<ver>.apk
# -> Failure [INSTALL_FAILED_<REASON>: ...]

Map the reason:

INSTALL_FAILED_* Cause Fix
UPDATE_INCOMPATIBLE … signatures do not match Old install signed with a different key (e.g. pre-shared-keystore per-machine key 58:31:12…). Uninstall the old package, then install. One-time per device after a key change.
INVALID_APK / parse error Corrupt/incomplete download or bad signing. Re-download; re-run the publish script.
INSUFFICIENT_STORAGE Storage. Free space.
OLDER_SDK Device below minSdk (26 = Android 8.0). Unsupported device.

A manual uninstall on the phone may NOT clear UPDATE_INCOMPATIBLE if the package is registered under another user/profile — pm path <pkg> under user 0 can show nothing while the conflict persists. adb uninstall <pkg> clears it across all users.

Phone / adb safety (non-negotiable)

When acting on the user's physical phone, be surgical — the user once had all home-screen app layouts wiped by an over-broad action.

  • Default to read-only adb (devices, getprop, pm path/list, dumpsys).
  • Mutations (adb install, adb uninstall com.archipelago.app.debug) only with explicit go-ahead and scoped to our exact package — echo it first.
  • Never run launcher/system resets: no pm clear on launchers, no reset-permissions, no factory wipe, no uninstalling apps you didn't build.

Verify the published download after shipping

The download served to nodes is Gitea raw-on-main. Confirm the live bytes match what you built and signed:

SERVED=neode-ui/public/packages/archipelago-companion.apk
URL=http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/$SERVED
curl -sS -o /tmp/live.apk "$URL"
shasum -a 256 "$SERVED" /tmp/live.apk          # must match
apksigner verify -v --min-sdk-version 21 /tmp/live.apk | grep -i "scheme"  # v1/v2/v3 = true