diff --git a/AGENTS.md b/AGENTS.md index 87dc4af..7c5782e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -225,16 +225,19 @@ first if a remote spawn ever mangles an argument. The emulator app reaches it at `https://10.0.2.2:8443`; enroll it with `adb -s "$SERIAL" shell "am start -a android.intent.action.VIEW -d 'aiapp://enroll?host=10.0.2.2&port=8443&token=…'"` (quote so the device shell doesn't eat the `&`s). -- **Name the device on every `adb` call.** This checkout runs its own - emulator — `run-android.sh` derives the AVD name from the directory, so - a second clone gets a second AVD rather than queueing for one shared - machine-wide `tdep`. With more than one attached, a bare `adb shell` or - `adb get-state` fails with `more than one device/emulator` and a bare - `adb shell pm list packages` comes back **empty**, which reads as the app - having been uninstalled rather than as the question being ambiguous. - Get the serial the way `run-android.sh` does — match `adb -s emu - avd name` against the AVD — or export `ANDROID_SERIAL`, which every - `adb` call honours without `-s`. +- **The emulator is `~/repos/emulator-tools`' business, not this repo's.** + `emu up` creates and boots the AVD named after this checkout (`ai-app-2`), + refusing when the machine has no room for one; `emu list` says what is + attached and what it costs; `emu down` stops it. `run-android.sh` is that + plus a build and an install. Run that repo's `install.sh` once if `emu` is + missing. + The `adb` on `PATH` after sourcing `android-env.sh` is that repo's wrapper, + which fills in `-s` from the same rule — so a bare `adb shell` reaches this + checkout's emulator and refuses to reach another one's. That defaulting is + what makes the old advice unnecessary rather than wrong: with two attached + and no `-s`, a bare `adb shell pm list packages` comes back **empty**, + which reads as the app having been uninstalled rather than as the question + being ambiguous. ## Where things run (host vs this VM) diff --git a/app/run-android.sh b/app/run-android.sh index 0da7949..3b65dae 100755 --- a/app/run-android.sh +++ b/app/run-android.sh @@ -1,7 +1,13 @@ #!/bin/sh -# Builds and runs this app on an emulator, creating/booting the AVD first if -# it isn't already up. Same flow as dev-updater's run-android.sh; see that -# script for the reasoning behind the avd handling. +# Builds this app and runs it on this checkout's emulator. +# +# The emulator half of this -- which AVD this checkout means, creating it, +# booting it headless, and refusing to start one the machine has no room for +# -- lives in ~/repos/emulator-tools and is shared with every other Android +# checkout here. This script kept its own copy of that sequence until +# 2026-08-30, as did dev-updater's and ai-app's, and three copies of "boot an +# emulator" is three places for the memory check that was missing from all of +# them. # # Environment setup (SDK location, PATH, ...) lives in ./android-env.sh, # which can also be sourced directly for one-off commands. @@ -9,105 +15,34 @@ set -eu APP_ID="com.example.aiapp" -DEVICE_PROFILE="${DEVICE_PROFILE:-pixel_10}" -SYSTEM_IMAGE="${SYSTEM_IMAGE:-system-images;android-36;google_apis;x86_64}" - SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) cd "$SCRIPT_DIR" -# One AVD per checkout, named after it -- so two clones of this repo, or a -# clone and a worktree, each get their own rather than fighting over one. -# This used to default to a machine-wide "tdep", which made the emulator the -# one thing here that could not be worked on in parallel: taking it meant -# asking whoever had it, waiting, and handing it back, and installing onto a -# running one steals the foreground from whatever they were looking at. -# Derived rather than written down, so neither clone names the other's. -# Override with AVD_NAME=... to share one deliberately. -AVD_NAME="${AVD_NAME:-$(basename "$(dirname "$SCRIPT_DIR")")}" - # shellcheck source=./android-env.sh . ./android-env.sh -# Prints the adb serial of a running instance of AVD "$1", or nothing. -avd_serial() { - for s in $(adb devices | awk '$2 == "device" {print $1}'); do - if [ "$(adb -s "$s" emu avd name 2>/dev/null | head -n1 | tr -d '\r')" = "$1" ]; then - echo "$s" - return 0 - fi - done -} - -echo "==> Ensuring emulator system image is installed" -android sdk install emulator "$SYSTEM_IMAGE" || echo " (non-fatal: see above)" - -if [ ! -f "$ANDROID_AVD_HOME/$AVD_NAME.ini" ]; then - echo "==> Creating AVD '$AVD_NAME' ($DEVICE_PROFILE, $SYSTEM_IMAGE)" - echo no | avdmanager create avd \ - -n "$AVD_NAME" \ - -k "$SYSTEM_IMAGE" \ - --device "$DEVICE_PROFILE" \ - --sdcard 512M -else - echo "==> Reusing existing AVD '$AVD_NAME'" +if ! command -v emu >/dev/null 2>&1; then + echo "run-android.sh: no 'emu' command." >&2 + echo " It comes from ~/repos/emulator-tools; run that repo's ./install.sh." >&2 + exit 127 fi -# Host keyboard into the emulator -- this app has text fields. -CONFIG_INI="$ANDROID_AVD_HOME/$AVD_NAME.avd/config.ini" -if [ -f "$CONFIG_INI" ]; then - grep -v '^hw\.keyboard=' "$CONFIG_INI" >"$CONFIG_INI.tmp" - echo "hw.keyboard=yes" >>"$CONFIG_INI.tmp" - mv "$CONFIG_INI.tmp" "$CONFIG_INI" -fi - -SERIAL=$(avd_serial "$AVD_NAME") -if [ -n "$SERIAL" ]; then - echo "==> Emulator '$AVD_NAME' already running ($SERIAL)" -else - # Clean up a stray/crashed process for this AVD, if any. The bracketed - # first character keeps the pattern from matching the shell running - # this script -- unbracketed, this kills that shell mid-run. - pkill -f "[e]mulator.*-avd $AVD_NAME" >/dev/null 2>&1 || true - - EMU_LOG="/tmp/$AVD_NAME-emulator.log" - : >"$EMU_LOG" - if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then - echo "==> Starting emulator '$AVD_NAME' with GPU acceleration (-gpu host)" - emulator -avd "$AVD_NAME" -gpu host -no-audio >"$EMU_LOG" 2>&1 & - else - echo "==> No display -- starting emulator '$AVD_NAME' headless (-gpu swiftshader_indirect)" - emulator -avd "$AVD_NAME" -gpu swiftshader_indirect -no-audio -no-window \ - >"$EMU_LOG" 2>&1 & - fi - EMU_PID=$! - - i=0 - booted="" - while [ "$i" -lt 150 ]; do - if ! kill -0 "$EMU_PID" 2>/dev/null; then - echo "Emulator process exited unexpectedly. Log output:" >&2 - cat "$EMU_LOG" >&2 - exit 1 - fi - SERIAL=$(avd_serial "$AVD_NAME") - if [ -n "$SERIAL" ]; then - booted=$(adb -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') - [ "$booted" = "1" ] && break - fi - i=$((i + 1)) - sleep 2 - done - if [ "$booted" != "1" ]; then - echo "Emulator did not finish booting in time. Log output:" >&2 - cat "$EMU_LOG" >&2 - exit 1 - fi -fi +# Prints the serial, having created and booted the AVD if it had to. Named +# after the checkout, so this cannot land on another session's emulator -- +# and refuses rather than starting one when the machine is short of memory, +# because what an OOM kills is somebody else's work rather than the emulator +# that asked for the memory. +echo "==> Emulator" +SERIAL=$(emu up) +export ANDROID_SERIAL="$SERIAL" echo "==> Building debug APK" ./gradlew :androidApp:assembleDebug APK="androidApp/build/outputs/apk/debug/androidApp-debug.apk" echo "==> Installing and launching $APK" -adb -s "$SERIAL" install -r "$APK" -adb -s "$SERIAL" shell am start -n "$APP_ID/.MainActivity" +# ANDROID_SERIAL above is what aims these; the adb wrapper would work it out +# from the checkout anyway, but a script that says which device it means does +# not depend on being run from the right directory. +adb install -r "$APK" +adb shell am start -n "$APP_ID/.MainActivity"