#!/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 }" 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 |screenshot [out]|stop|status" exit 2 ;; esac