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>
167 lines
5.5 KiB
Bash
Executable File
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
|