diff options
Diffstat (limited to 'bin/android-ui')
| -rwxr-xr-x | bin/android-ui | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/bin/android-ui b/bin/android-ui new file mode 100755 index 0000000..25257f1 --- /dev/null +++ b/bin/android-ui @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# android-ui <package> <command> [args] +# +# dump list the on-screen text and content-descriptions +# tap <description> tap the element whose content-desc or text matches, exactly once +# shot <file> screenshot, but only after confirming <package> is foreground +# foreground print the foreground package and exit non-zero if it isn't <package> +# +# Exists because a blind `adb shell input tap X Y` during this project landed on a completely +# different app's settings screen, and the screenshot taken straight afterwards was reported as +# the app under test. Coordinates are guesses about a layout; element descriptions are what the +# app actually published. Every command here verifies the foreground package first, so a tap can +# never silently drive something else. +set -uo pipefail + +ADB=${ADB:-/opt/android-sdk/platform-tools/adb} +LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)" +die() { echo "android-ui: $1" >&2; exit 2; } + +[ -x "$ADB" ] || die "adb not found at $ADB (set ADB=...)" +[ -x "$LIB/uinodes.py" ] || die "missing $LIB/uinodes.py" +[ $# -ge 2 ] || die "usage: android-ui <package> <dump|tap|shot|foreground> [args]" + +PKG="$1"; CMD="$2"; shift 2 + +foreground_pkg() { + "$ADB" shell dumpsys activity activities 2>/dev/null \ + | grep -oE 'topResumedActivity=ActivityRecord\{[^}]*' \ + | head -1 \ + | grep -oE '[a-zA-Z0-9_.]+/[a-zA-Z0-9_.]+' \ + | head -1 \ + | cut -d/ -f1 +} + +require_foreground() { + local actual + actual=$(foreground_pkg) + if [ "$actual" != "$PKG" ]; then + echo "android-ui: foreground is '${actual:-unknown}', expected '$PKG'" >&2 + echo " Nothing was done. Bring the app forward first; a tap here would have driven" >&2 + echo " whatever else is on screen." >&2 + exit 1 + fi +} + +dump_xml() { + "$ADB" shell uiautomator dump /sdcard/android-ui.xml >/dev/null 2>&1 \ + || die "uiautomator dump failed" + "$ADB" shell cat /sdcard/android-ui.xml 2>/dev/null +} + +case "$CMD" in + foreground) + actual=$(foreground_pkg) + echo "foreground: ${actual:-unknown}" + echo "expected : $PKG" + [ "$actual" = "$PKG" ] && { echo "result : MATCH"; exit 0; } + echo "result : MISMATCH"; exit 1 + ;; + + dump) + require_foreground + dump_xml | "$LIB/uinodes.py" list + ;; + + tap) + [ $# -ge 1 ] || die "tap needs a label" + require_foreground + TARGET="$1" + COORDS=$(dump_xml | "$LIB/uinodes.py" find "$TARGET") + + case "$COORDS" in + NONE) + echo "android-ui: nothing on screen is labelled '$TARGET'" >&2 + echo " Run: android-ui $PKG dump" >&2 + exit 1 ;; + AMBIGUOUS*) + # Tapping the first of several is how you act on the wrong row. + echo "android-ui: '$TARGET' matches ${COORDS#AMBIGUOUS } places; refusing to guess" >&2 + echo " Run: android-ui $PKG dump" >&2 + exit 1 ;; + esac + + "$ADB" shell input tap $COORDS >/dev/null 2>&1 || die "tap failed" + echo "tapped '$TARGET' at ($COORDS)" + ;; + + shot) + [ $# -ge 1 ] || die "shot needs an output path" + require_foreground + "$ADB" exec-out screencap -p > "$1" || die "screencap failed" + SIZE=$(stat -c%s "$1" 2>/dev/null || echo 0) + [ "$SIZE" -gt 1000 ] || die "screenshot is $SIZE bytes; capture failed" + echo "captured $1 ($SIZE bytes) with $PKG in the foreground" + ;; + + *) die "unknown command '$CMD'" ;; +esac |
