blob: 25257f13663f781d9f3d2004c0af60513d97ccca (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
|