#!/bin/sh # Single-emulator self-mirror E2E. # # Boot an AOSP API-35 emulator, root it, install the app, let it # generate its keypair, copy that pubkey into /data/misc/adb/adb_keys # so the app can connect to its own host's adbd over TCP, launch # Mirror against 127.0.0.1:5555, wait for the first video frame in # logcat, screencap, assert the frame is non-uniform. # # Runs inside the test image; orchestrated by ../test. set -eu ROOT=/work # Debug applicationId (note the .debug suffix). Activity classes live in # the invalid.lena.scrcpy namespace, which carries no suffix. PKG=invalid.lena.scrcpy.debug AVD=scrcpy-test SERIAL=emulator-5554 APK=$ROOT/app/build/outputs/apk/debug/app-debug.apk TMPDIR=$ROOT/.tools/e2e mkdir -p "$TMPDIR" log() { printf 'e2e: %s\n' "$*" >&2; } # ---- 0. server jar ---- [ -f "$ROOT/app/src/main/assets/scrcpy-server.jar" ] || "$ROOT/scripts/update-server" # ---- 1. build APK ---- log 'gradle :app:assembleDebug' "$ROOT/gradlew" --no-daemon -q :app:assembleDebug # ---- 2. AVD + boot ---- if ! avdmanager list avd | grep -q "Name: $AVD$"; then log "creating avd $AVD" echo no | avdmanager create avd -n "$AVD" -k 'system-images;android-35;default;x86_64' -d pixel >/dev/null fi # Clean stale lock files from a previously aborted run; otherwise the # emulator refuses to start ("Running multiple emulators with the same AVD"). AVD_DIR="$ANDROID_AVD_HOME/$AVD.avd" rm -f "$AVD_DIR"/multiinstance.lock "$AVD_DIR"/hardware-qemu.ini.lock 2>/dev/null || true rm -rf "$HOME/.android/avd/running" 2>/dev/null || true if ! pgrep -f "emulator.*-avd $AVD" >/dev/null; then log 'booting emulator (no window, no snapshot)' emulator -avd "$AVD" -no-window -no-audio -no-snapshot -gpu swiftshader_indirect \ -no-boot-anim -accel on >"$TMPDIR/emulator.log" 2>&1 & fi log 'wait-for-device' adb -s "$SERIAL" wait-for-device log 'wait for boot' until [ "$(adb -s "$SERIAL" shell getprop sys.boot_completed | tr -d '\r')" = 1 ]; do sleep 2 done # ---- 3. root (emulator adbd already listens on TCP 5555) ---- log 'adb root' adb -s "$SERIAL" root adb -s "$SERIAL" wait-for-device # ---- 4. install ---- log "install $APK" adb -s "$SERIAL" install -r "$APK" >/dev/null # ---- 5. let the app generate its keypair ---- log "launching Main once so Adb.java writes filesDir/adbkey" adb -s "$SERIAL" shell am start -W -n "$PKG/invalid.lena.scrcpy.Main" >/dev/null # Adb.java writes the keypair inside its constructor, before setContentView sleep 1 # ---- 6. extract pubkey and authorise ---- log 'reading filesDir/adbkey via run-as' adb -s "$SERIAL" shell "run-as $PKG cat files/adbkey" > "$TMPDIR/adbkey.der" if [ ! -s "$TMPDIR/adbkey.der" ]; then log 'adbkey was empty - did Adb.java run?' adb -s "$SERIAL" logcat -d -t 200 -s scrcpy-android >&2 || true exit 1 fi log 'computing android pubkey blob' java "$ROOT/test-rig/Keygen.java" "$TMPDIR/adbkey.der" > "$TMPDIR/adb_keys.line" log 'appending to /data/misc/adb/adb_keys' adb -s "$SERIAL" push "$TMPDIR/adb_keys.line" /sdcard/adb_keys.line >/dev/null adb -s "$SERIAL" shell "cat /sdcard/adb_keys.line >> /data/misc/adb/adb_keys \ && chown system:shell /data/misc/adb/adb_keys \ && chmod 640 /data/misc/adb/adb_keys \ && rm /sdcard/adb_keys.line" # adbd rereads /data/misc/adb/adb_keys on every AUTH challenge, # so no daemon restart is necessary. Restarting it would put the # host's adb client into 'offline' state and force a reconnect dance. # ---- 7. trigger Mirror ---- log 'starting Mirror -> 127.0.0.1:5555' adb -s "$SERIAL" logcat -c adb -s "$SERIAL" shell am start -n "$PKG/invalid.lena.scrcpy.Mirror" \ --es host 127.0.0.1 --ei port 5555 >/dev/null # ---- 8. wait for first frame, fail on session give-up ---- # Server's openAbstract budget (Server.OPEN_DEADLINE_MS) must stay # shorter than this deadline; otherwise our retries finish before # we've given the wire a chance. deadline=$(( $(date +%s) + ${E2E_DEADLINE:-60} )) ok= while [ "$(date +%s)" -lt "$deadline" ]; do line=$(adb -s "$SERIAL" logcat -d -s scrcpy-android | tail -200) if printf '%s' "$line" | grep -q 'video frame n=1'; then ok=1; break fi if printf '%s' "$line" | grep -q 'session: gave up'; then log 'session gave up; logcat:' printf '%s\n' "$line" | tail -50 >&2 exit 1 fi sleep 1 done if [ -z "$ok" ]; then log 'timed out waiting for video frame n=1' log "full app log: $TMPDIR/logcat.scrcpy" adb -s "$SERIAL" logcat -d -s scrcpy-android > "$TMPDIR/logcat.scrcpy" 2>&1 || true log "full system log: $TMPDIR/logcat.full" adb -s "$SERIAL" logcat -d > "$TMPDIR/logcat.full" 2>&1 || true log 'last 80 scrcpy-android lines:' tail -80 "$TMPDIR/logcat.scrcpy" >&2 || true exit 1 fi log 'video frame n=1 - proceeding to screencap' # Give the decoder a moment to actually render to the surface. sleep 1 # ---- 9. screencap + pixhash ---- adb -s "$SERIAL" exec-out screencap -p > "$TMPDIR/shot.png" log "screencap $(wc -c <"$TMPDIR/shot.png") bytes" java "$ROOT/test-rig/Pixhash.java" "$TMPDIR/shot.png" --min 5 # ---- 9b. mid-session disconnect -> auto-reconnect ---- # Kill the scrcpy server on the target out from under the app. The video # socket dies; Session.run() must detect it, rerun the bring-up ladder, # and a fresh VideoStream must log 'video frame n=1' again. Clear logcat # first so the only frame we can match is the post-reconnect one. log 'forcing a mid-session disconnect (pkill scrcpy server on target)' adb -s "$SERIAL" logcat -c adb -s "$SERIAL" shell 'pkill -f com.genymobile.scrcpy.Server' || true rdeadline=$(( $(date +%s) + ${E2E_RECONNECT_DEADLINE:-60} )) relink= while [ "$(date +%s)" -lt "$rdeadline" ]; do if adb -s "$SERIAL" logcat -d -s scrcpy-android | grep -q 'video frame n=1'; then relink=1; break fi sleep 1 done if [ -z "$relink" ]; then log 'timed out waiting for auto-reconnect (no fresh video frame n=1)' adb -s "$SERIAL" logcat -d -s scrcpy-android > "$TMPDIR/logcat.reconnect" 2>&1 || true log "full app log: $TMPDIR/logcat.reconnect" tail -80 "$TMPDIR/logcat.reconnect" >&2 || true exit 1 fi log 'auto-reconnect ok - fresh video frame n=1 after link loss' # ---- 10. cleanup ---- log "full app log: $TMPDIR/logcat.scrcpy" adb -s "$SERIAL" logcat -d -s scrcpy-android > "$TMPDIR/logcat.scrcpy" 2>&1 || true log 'adb emu kill' adb -s "$SERIAL" emu kill || true log 'e2e: pass'