#!/bin/bash # adb, aimed at this checkout's own emulator, with screenshots scaled down # before anything reads them. # # This is a wrapper, not a separate screenshot command, because a separate # command is a rule to remember and this has to hold for every agent on # this machine without anybody opting in. `adb exec-out screencap -p` is # the normal way to take a screenshot here, so that is the thing that has # to come back small. # # What it costs to read one is set by pixels, not bytes: an image is # charged at about (width x height) / 750 tokens, after being scaled to fit # 1568px on its long edge. So a 1080x2424 phone screenshot is ~1460 tokens # however well the PNG is compressed, and the only lever that moves is the # size. At 800px on the long edge it is ~380 -- and a 356x800 frame is # still legible enough to check layout, contrast, alignment and small # labels, which is what these are looked at for. Raise it for one call with # ADB_SCREENCAP_MAX_EDGE if something genuinely needs the detail. # # The other half is which device a call means. Several agent sessions share # this machine, each with an emulator named after its checkout, and a bare # `adb shell` with two of them attached does not fail cleanly: `adb shell pm # list packages` comes back *empty*, which reads as the app having been # uninstalled rather than as the question being ambiguous. So when a call # does not name a device and this checkout's own emulator is running, this # aims at it. Nothing is guessed when it is not running: adb's own error is # better than a wrapper picking somebody else's phone. # # Everything that is not a screencap is handed straight through, unchanged, # including its exit status -- a wrapper that breaks `adb install` to save # tokens on screenshots would not survive the week. set -euo pipefail . "$(dirname "$(readlink -f "$0")")/../lib/project-avd.sh" # See `android_adb`: found without consulting PATH, which is how this script # was called and would be how it called itself for ever. real=$(android_adb) || { echo "adb: no platform-tools under any known SDK; set ANDROID_HOME" >&2 exit 127 } # Aim at this checkout's emulator, unless the caller has already said which # device they mean or is asking something that is not about one. # # `-s` and friends are only valid before the subcommand, so the scan stops at # the first word that is not one of adb's own options -- otherwise # `adb shell echo -s` would look like a device selection. target=() if [ -z "${ANDROID_SERIAL:-}" ]; then subcommand="" named=false for arg in "$@"; do case "$arg" in -s | -d | -e | -t | -H | -P | -L) named=true break ;; -*) ;; *) subcommand="$arg" break ;; esac done case "$subcommand" in # Not about one device: `devices` lists them all, and the rest talk to # the adb server itself. Passing -s to these is at best ignored. devices | start-server | kill-server | version | help | connect | disconnect) ;; *) if [ "$named" = false ]; then serial=$(avd_serial "$real" "$(project_avd)" || true) [ -n "$serial" ] && target=(-s "$serial") fi ;; esac fi screencap=false for arg in "$@"; do if [ "$arg" = screencap ]; then screencap=true break fi done # ffmpeg missing is a reason to hand over the full-size image, not to fail: # the caller asked for a screenshot and is entitled to one. if [ "$screencap" != true ] || ! command -v ffmpeg >/dev/null 2>&1; then exec "$real" "${target[@]}" "$@" fi max=${ADB_SCREENCAP_MAX_EDGE:-800} tmp=$(mktemp -t adb-screencap.XXXXXX.png) trap 'rm -f "$tmp"' EXIT status=0 "$real" "${target[@]}" "$@" >"$tmp" || status=$? # Only a PNG that actually arrived on stdout is ours to shrink. `adb shell # screencap -p /sdcard/x.png` writes to the device and produces nothing # here, and feeding that to ffmpeg would turn a working command into a # failing one; so anything that is not a PNG goes back out byte for byte, # with the real adb's exit status. if [ "$status" -ne 0 ] || [ ! -s "$tmp" ] || [ "$(head -c 8 "$tmp" | od -An -tx1 | tr -d ' \n')" != "89504e470d0a1a0a" ]; then cat "$tmp" exit "$status" fi # Never enlarge (min(1,...)), keep the aspect ratio, and keep both edges # even, which some encoders insist on. If ffmpeg cannot make sense of it, # the original still goes out: a smaller screenshot is worth having, a # missing one is not. scale="min(1,$max/max(iw,ih))" if ! ffmpeg -y -loglevel error -i "$tmp" \ -vf "scale='trunc(iw*$scale/2)*2':'trunc(ih*$scale/2)*2':flags=area" \ -f image2pipe -c:v png -; then cat "$tmp" fi