#!/bin/sh # Builds and runs this app on an emulator, creating/booting the AVD first if # it isn't already up. # # Environment setup (SDK location, PATH, ...) lives in ./android-env.sh, # which can also be sourced directly for one-off commands; see its header # comment. The emulator handling below is inline rather than in its own # shared script because this repo has exactly one app, so there's no second # caller to share it with. # # Goes through classic avdmanager/emulator/adb rather than the newer # `android` CLI's emulator subsystem: that one manages its own AVD pool and # would start a *second* instance alongside whatever is already running. set -eu APP_ID="com.example.devupdater" # One AVD per repository, named after it, which is how the other Android # projects on this machine are set up: two sessions working in two repos # otherwise fight over one emulator instance, and neither can tell that the # app it just installed was replaced by the other one's. # This app installs *other* projects' builds, so exercising it needs # something to install -- but that something should be a throwaway app you # control, added to the list like any other project, not another repo's real # app sharing this emulator. AVD_NAME=... overrides the name if you do need # a second one. AVD_NAME="${AVD_NAME:-dev-updater}" 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" # shellcheck source=./android-env.sh . ./android-env.sh # Prints the adb serial of a running instance of AVD "$1", or nothing. Unlike # `adb -e` (which only works when exactly one emulator is attached, and # can't tell $AVD_NAME apart from some other AVD attached separately), this # checks by name so it can't mistake someone else's emulator for ours. 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'" fi # avdmanager defaults new AVDs to hw.keyboard=no, which disables forwarding # the host keyboard into the emulator and leaves you dependent on the # on-screen keyboard. Force it on so typing works -- this app has a # free-text path field, so that matters more here than most. 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, so it doesn't # end up with two instances fighting over the same AVD directory. The # bracketed first character keeps the pattern from matching the shell # running this script, which has the pattern text on its own command # line -- 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" # Real GPU acceleration when a display is available; headless software # rendering otherwise (e.g. a VM with no display attached). 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 available (DISPLAY/WAYLAND_DISPLAY unset) -- starting" \ "emulator '$AVD_NAME' headless with software rendering (-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 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"