Files
Builder 0874292172 Tooling: emu.sh selects the emulator explicitly
The Pixel 8a is attached to .27 at the same time as the headless
emulator, so bare adb calls failed with
"error: more than one device/emulator". Every device-specific adb call
now gets an explicit serial.

- Add serial selection: ANDROID_SERIAL when set, else the first
  emulator-* entry from "adb devices".
- Pass -s "$SERIAL" to the adb calls in install, screenshot, status and
  stop.
- wait polls for the emulator serial instead of bare
  "adb wait-for-device", which fails at once when the Pixel 8a is
  attached, then waits with -s and accepts an optional timeout.
- Fail with a clear message, and not a raw adb error, when no emulator
  runs; screenshot also fails on an empty capture.
- Interface unchanged: start|wait|install <apk>|screenshot [out]|stop|status.

Proven on .27 with the Pixel 8a attached (USB and wireless):
install -> Success, screenshot -> 1392860 bytes from emulator-5554.
The deployed copy at ~/android/emu.sh is byte-identical to this file.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-14 14:52:37 +11:00

167 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# emu.sh — headless Android emulator helper on .27.
#
# The physical Pixel 8a is normally attached to .27 (USB and/or wireless) at the
# same time as the emulator. adb therefore refuses bare commands with
# "error: more than one device/emulator". Every device-specific adb call below
# is given an explicit serial.
#
# Serial selection order:
# 1. $ANDROID_SERIAL, when set (a serial or an emulator port).
# 2. The first emulator-* entry in "adb devices".
# Commands that need a device fail with a clear message when no emulator runs.
#
# Called from .13, e.g.:
# ssh 192.168.20.27 '~/android/emu.sh start'
# ssh 192.168.20.27 '~/android/emu.sh wait'
# ssh 192.168.20.27 '~/android/emu.sh install /home/sam/app-debug.apk'
# ssh 192.168.20.27 '~/android/emu.sh screenshot /tmp/shot.png'
# ssh 192.168.20.27 '~/android/emu.sh status'
# ssh 192.168.20.27 '~/android/emu.sh stop'
#
# Source of truth for this copy: sam/kotlin_phone_app_admin, android/tooling/emu.sh
set -euo pipefail
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
AVD="${AVD_NAME:-pixel35}"
LOG="/tmp/emu-$AVD.log"
# Serial of the emulator this script acts on. Prints nothing when no emulator
# entry is present. Matches any state (device, offline) so that "wait" can pick
# up an emulator that is still booting.
select_serial() {
if [ -n "${ANDROID_SERIAL:-}" ]; then
printf '%s\n' "$ANDROID_SERIAL"
return 0
fi
adb devices 2>/dev/null | awk '$1 ~ /^emulator-/ { print $1; exit }'
}
# State column of one serial in "adb devices": device, offline, unauthorized,
# or empty when the serial is not listed.
device_state() {
adb devices 2>/dev/null | awk -v s="$1" '$1 == s { print $2; exit }'
}
# Echo the selected serial, or exit 1 with a clear message. Used by every
# command that must talk to a specific device.
require_serial() {
local serial state
serial="$(select_serial)"
if [ -z "$serial" ]; then
echo "ERROR: no emulator is running (no emulator-* device, and ANDROID_SERIAL is unset)." >&2
echo " Start one with: $0 start" >&2
echo " 'adb devices' says:" >&2
adb devices >&2
exit 1
fi
state="$(device_state "$serial")"
if [ -z "$state" ]; then
echo "ERROR: serial '$serial' is not attached. 'adb devices' says:" >&2
adb devices >&2
exit 1
fi
if [ "$state" != "device" ]; then
echo "ERROR: '$serial' is '$state', not ready." >&2
echo " Wait for it with: $0 wait" >&2
exit 1
fi
printf '%s\n' "$serial"
}
case "${1:-status}" in
start)
if adb devices 2>/dev/null | awk '$1 ~ /^emulator-/ { found = 1 } END { exit !found }'; then
echo "already running ($(select_serial))"
exit 0
fi
setsid nohup "$ANDROID_HOME/emulator/emulator" -avd "$AVD" \
-no-window -no-audio -no-snapshot -no-boot-anim -gpu swiftshader_indirect \
> "$LOG" 2>&1 &
echo "starting $AVD (log: $LOG)"
;;
wait)
boot_timeout="${2:-420}"
deadline=$(( $(date +%s) + boot_timeout ))
# Poll for a serial instead of using bare "adb wait-for-device": with the
# Pixel 8a attached, bare adb fails at once with
# "error: more than one device/emulator", even before the emulator appears.
serial="$(select_serial)"
while [ -z "$serial" ]; do
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "ERROR: no emulator-* device appeared within ${boot_timeout}s. 'adb devices' says:" >&2
adb devices >&2
echo " Start one with: $0 start" >&2
exit 1
fi
sleep 2
serial="$(select_serial)"
done
remaining=$(( deadline - $(date +%s) ))
[ "$remaining" -gt 0 ] || remaining=1
timeout "$remaining" adb -s "$serial" wait-for-device \
|| { echo "ERROR: emulator $serial did not come online within ${boot_timeout}s"; exit 1; }
n=0
while [ "$n" -lt 72 ]; do
if [ "$(adb -s "$serial" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; then
echo "booted ($serial)"
exit 0
fi
n=$((n + 1))
sleep 5
done
echo "ERROR: boot timeout (6 minutes) waiting for $serial"
exit 1
;;
install)
apk="${2:?usage: emu.sh install <apk>}"
if [ ! -f "$apk" ]; then echo "ERROR: no such apk: $apk"; exit 1; fi
serial="$(require_serial)"
adb -s "$serial" install -r "$apk"
;;
screenshot)
out="${2:-/tmp/emulator-screen.png}"
serial="$(require_serial)"
adb -s "$serial" exec-out screencap -p > "$out"
size="$(stat -c%s "$out")"
if [ "$size" -eq 0 ]; then
echo "ERROR: screencap from $serial produced an empty file: $out" >&2
exit 1
fi
echo "wrote $out ($size bytes) from $serial"
;;
stop)
serial="$(select_serial)"
if [ -n "$serial" ]; then
adb -s "$serial" emu kill >/dev/null 2>&1 || true
fi
sleep 2
pkill -f qemu-system-x86_64 >/dev/null 2>&1 || true
echo "stopped"
;;
status)
serial="$(select_serial)"
adb devices
if [ -z "$serial" ]; then
echo "ERROR: no emulator is running (no emulator-* device, and ANDROID_SERIAL is unset)." >&2
echo " Start one with: $0 start" >&2
exit 1
fi
state="$(device_state "$serial")"
printf "emulator=%s state=%s android=" "$serial" "$state"
adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r'
[ "$state" = "device" ] || exit 1
;;
*)
echo "usage: $0 start|wait|install <apk>|screenshot [out]|stop|status"
exit 2
;;
esac