outputs.apps.x86_64.screenshot = { type = "app"; program = toString (pkgs.writeShellScript "screenshot-script" '' set -euo pipefail EMU_SDK="${androidEmulatorComposition.androidsdk}/libexec/android-sdk" ADB="$EMU_SDK/platform-tools/adb" EMULATOR="$EMU_SDK/emulator/emulator" APK="${self.packages.${system}.debug}/myapp-debug.apk" SRC_DIR="$(${pkgs.git}/bin/git rev-parse --show-toplevel)" AVD_HOME="$(mktemp -d)" trap 'kill "$EMU_PID" 2>/dev/null; wait "$EMU_PID" 2>/dev/null; rm -rf "$AVD_HOME"' EXIT # Create AVD AVD_DIR="$AVD_HOME/screenshot.avd" mkdir -p "$AVD_DIR" cat > "$AVD_HOME/screenshot.ini" <<EOF avd.ini.encoding=UTF-8 path=$AVD_DIR target=android-${platformVersion} EOF cat > "$AVD_DIR/config.ini" <<EOF AvdId=screenshot PlayStore.enabled=false abi.type=x86_64 avd.ini.encoding=UTF-8 hw.cpu.arch=x86_64 hw.gpu.enabled=yes hw.gpu.mode=swiftshader_indirect hw.lcd.density=420 hw.lcd.height=2400 hw.lcd.width=1080 hw.ramSize=2048 image.sysdir.1=system-images/android-${platformVersion}/google_apis/x86_64/ skin.dynamic=yes tag.display=Google APIs tag.id=google_apis disk.dataPartition.size=2G EOF echo "==> Starting emulator..." ANDROID_AVD_HOME="$AVD_HOME" ANDROID_HOME="$EMU_SDK" \ "$EMULATOR" -avd screenshot -no-window -no-audio -no-boot-anim \ -gpu swiftshader_indirect -no-snapshot 2>&1 & EMU_PID=$! echo "==> Waiting for boot..." for i in $(seq 1 90); do BOOT=$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') || true if [ "$BOOT" = "1" ]; then echo " Booted after ~$((i * 2))s" break fi sleep 2 done if [ "$BOOT" != "1" ]; then echo "ERROR: Emulator failed to boot" >&2 exit 1 fi # Enable dark mode "$ADB" shell cmd uimode night yes # Install and launch echo "==> Installing APK..." "$ADB" install -r "$APK" "$ADB" shell pm grant com.me.myapp android.permission.WRITE_SECURE_SETTINGS "$ADB" shell am start -n com.me.myapp/.MainActivity sleep 3 # Navigate to settings screen by tapping "Notification Filters" button # This uses uiautomator to find the button by text for robustness "$ADB" shell uiautomator dump /sdcard/ui.xml BOUNDS=$("$ADB" shell cat /sdcard/ui.xml \ | ${pkgs.gnugrep}/bin/grep -oP 'text="Notification Filters"[^>]*bounds="\K[^"]+' \ || true) if [ -z "$BOUNDS" ]; then echo "ERROR: Could not find Notification Filters button" >&2 exit 1 fi # Parse bounds "[x1,y1][x2,y2]" to compute center tap coordinates X1=$(echo "$BOUNDS" | ${pkgs.gnused}/bin/sed 's/\[\([0-9]*\),\([0-9]*\)\]\[\([0-9]*\),\([0-9]*\)\]/\1/') Y1=$(echo "$BOUNDS" | ${pkgs.gnused}/bin/sed 's/\[\([0-9]*\),\([0-9]*\)\]\[\([0-9]*\),\([0-9]*\)\]/\2/') X2=$(echo "$BOUNDS" | ${pkgs.gnused}/bin/sed 's/\[\([0-9]*\),\([0-9]*\)\]\[\([0-9]*\),\([0-9]*\)\]/\3/') Y2=$(echo "$BOUNDS" | ${pkgs.gnused}/bin/sed 's/\[\([0-9]*\),\([0-9]*\)\]\[\([0-9]*\),\([0-9]*\)\]/\4/') TAP_X=$(( (X1 + X2) / 2 )) TAP_Y=$(( (Y1 + Y2) / 2 )) "$ADB" shell input tap "$TAP_X" "$TAP_Y" sleep 2 # Capture and process screenshot echo "==> Capturing screenshot..." "$ADB" shell screencap -p /sdcard/screenshot.png "$ADB" pull /sdcard/screenshot.png "$AVD_HOME/raw.png" # Crop to content: remove status bar (top 128px) and empty space below # Per-App Overrides, then resize with high-quality Lanczos filter ${pkgs.imagemagick}/bin/magick "$AVD_HOME/raw.png" \ -crop 1080x1100+0+128 +repage \ -filter Lanczos -resize 540x \ "$SRC_DIR/fastlane/metadata/android/en-US/images/phoneScreenshots/settings.png" echo "==> Screenshot saved to fastlane/metadata/android/en-US/images/phoneScreenshots/settings.png" ''); };