#!/usr/bin/env bash # # Records the demonstration video that Google Play's foreground-service declaration asks for. # # ./tools/record-fgs-demo.sh [serial] [output-dir] # default: first adb device, ./store-assets/fgs-demo # # Play requires, per declared foreground-service type, "a link to a video demonstrating each # foreground service feature. The video should demonstrate the steps the user needs to take in # your app in order to trigger the feature." So this records an end-to-end *user journey*, not a # screen of a progress bar: # # app open on the empty state -> user taps Start scan -> user presses Home -> the ongoing # notification is visible in the shade while the scan keeps running -> back into the app for # the finished results # # A foreground service only exists while work is running, so this needs a real device or an # emulator and something to scan. `tools/make-demo-photos.py` supplies the library, so no real # photo is ever in the frame, and the library is deleted from the device again afterwards. # # Two failure modes this script works around, both learned the hard way on a software-emulated AVD # (no /dev/kvm, so everything below is slower than it looks): # # 1. `input tap` can land before the app is interactive and do nothing at all. Every tap is # therefore preceded by a `uiautomator dump` for the *live* coordinates and followed by a # check that the expected next screen appeared, with one retry. # 2. A finished scan leaves the app on its results screen, where "Start scan" no longer exists - # tapping those old coordinates silently does nothing. The recording therefore always starts # from `am force-stop` + `am start`, i.e. the documented empty state. # # It also writes a checkpoint PNG at each milestone, so the recording can be checked without # decoding the video. `ffprobe` is used for the duration when it is installed. set -euo pipefail here="$(cd "$(dirname "$0")" && pwd)" repo="$(dirname "$here")" pkg="de.softwareapp_hb.fotodup.finder.debug" activity="$pkg/de.softwareapp_hb.fotodup.finder.MainActivity" apk="${APK:-$repo/app/build/outputs/apk/debug/app-debug.apk}" serial="${1:-$(adb devices | awk 'NR==2 {print $1}')}" out="${2:-$repo/store-assets/fgs-demo}" remote=/data/local/tmp/fgs-demo.mp4 recording_seconds="${RECORDING_SECONDS:-150}" if [ -z "$serial" ]; then echo "usage: $0 [serial] [output-dir]" >&2 echo "error: no adb device or emulator attached" >&2 exit 2 fi adb=(adb -s "$serial") mkdir -p "$out" say() { printf '\n== %s\n' "$*"; } # --------------------------------------------------------------------- UI helpers # bounds_to_centre -> "x y" bounds_to_centre() { local x1 y1 x2 y2 # bounds is "[x1,y1][x2,y2]". Stripping everything but digits and commas is not enough: the # "][ " between the two corners disappears too, which glued y1 to x2 and produced coordinates # like "976 723061" - a tap that lands nowhere, with no error to show for it. read -r x1 y1 x2 y2 <<< "$(printf '%s' "$1" \ | sed -E 's/.*\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\].*/\1 \2 \3 \4/')" echo "$(( (x1 + x2) / 2 )) $(( (y1 + y2) / 2 ))" } node_by_pattern() { local bounds bounds="$(ui_dump | tr '>' '\n' | grep -i -m1 "$1" \ | grep -o 'bounds="\[[0-9]*,[0-9]*\]\[[0-9]*,[0-9]*\]"' | head -1 || true)" [ -n "$bounds" ] || return 1 bounds_to_centre "$bounds" } # node_centre -> "x y" for the first node whose text contains it. Substring match on purpose: # the screens label themselves ("Start scan", "Cancel"), but the results card says "2 duplicate # groups", which an exact match would never find. Anchors must not be passed in here - "^Cancel$" # would be compared literally against the attribute text. node_centre() { node_by_pattern "text=\"[^\"]*$1[^\"]*\""; } # node_centre_exact -> the same, but only for a node whose text *is* this. Needed for the # top bar's "Scan", which must not match the intro's "Start scan". node_centre_exact() { node_by_pattern "text=\"$1\""; } ui_dump() { "${adb[@]}" shell 'uiautomator dump /data/local/tmp/ui.xml >/dev/null 2>&1; cat /data/local/tmp/ui.xml' } # wait_for -> "x y" once a node whose text contains it appears. wait_for() { local deadline=$(( SECONDS + $2 )) coords while [ "$SECONDS" -lt "$deadline" ]; do if coords="$(node_centre "$1")"; then echo "$coords" return 0 fi sleep 3 done return 1 } # wait_for_exact -> the same for an exact text match. wait_for_exact() { local deadline=$(( SECONDS + $2 )) coords while [ "$SECONDS" -lt "$deadline" ]; do if coords="$(node_centre_exact "$1")"; then echo "$coords" return 0 fi sleep 3 done return 1 } scan_running() { "${adb[@]}" shell dumpsys activity services 2>/dev/null | grep -q -i ScanService; } notifications_from_app() { "${adb[@]}" shell dumpsys notification --noredact 2>/dev/null \ | grep -c 'NotificationRecord(.*fotodup' || true } checkpoint() { #