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>
This commit is contained in:
166
android/tooling/emu.sh
Executable file
166
android/tooling/emu.sh
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user