remote/tools/capture_evidence.ps1

144 lines
5.9 KiB
PowerShell
Raw Normal View History

# 2b evidence capture: byte-safe device screenshots of each acceptance state.
# ASCII-only source. Screenshots go through tools\shot.ps1 because PS 5.1 `>`
# writes UTF-16 and corrupts PNG bytes (see that file's header).
# Text/state assertions use uiautomator (lessons R-6: vivo logcat is throttled).
$ErrorActionPreference = "Stop"
$repo = (Resolve-Path "$PSScriptRoot\..").Path
$ev = "D:\working\projects\yuankong\evidence\2b"
$adb = "D:\working\android\sdk\platform-tools\adb.exe"
$phoneIp = "192.168.1.180"
function Adb-Shell {
param([Parameter(Mandatory)][string[]]$Cmd)
$old = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$out = & $adb shell @Cmd 2>$null
return @{ out = ($out -join "`n"); code = $LASTEXITCODE }
}
finally { $ErrorActionPreference = $old }
}
function Get-Ui {
[void](Adb-Shell @("uiautomator", "dump", "/sdcard/ui.xml"))
$old = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try { & $adb pull /sdcard/ui.xml "$env:TEMP\cap-ui.xml" 2>$null | Out-Null }
finally { $ErrorActionPreference = $old }
if (-not (Test-Path "$env:TEMP\cap-ui.xml")) { return "" }
return [System.IO.File]::ReadAllText("$env:TEMP\cap-ui.xml", [System.Text.Encoding]::UTF8)
}
function Wait-Live([int]$Seconds) {
$deadline = (Get-Date).AddSeconds($Seconds)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 900
$x = Get-Ui
if ($x -match "RTT=") { return $x }
}
return ""
}
function Shot([string]$name) {
$out = Join-Path $ev $name
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out $out
}
New-Item -ItemType Directory -Force -Path $ev | Out-Null
# drop the earlier broken captures (PS 5.1 '>' wrote them as UTF-16)
Get-ChildItem $ev -Filter "*.png" | ForEach-Object {
$b = [System.IO.File]::ReadAllBytes($_.FullName)
if ($b.Length -lt 4 -or ($b[0] -ne 0x89)) { Remove-Item $_.FullName -Force }
}
Write-Host "=== ensure fresh v3 agent ==="
$agentExe = "$repo\target\release\agent.exe"
$ag = Get-Process agent -ErrorAction SilentlyContinue
if ($ag -and (Get-Item $agentExe).LastWriteTime -gt $ag.StartTime) {
Stop-Process -Name agent -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 800; $ag = $null
}
if (-not $ag) {
Start-Process $agentExe -ArgumentList "--port","47800","--code","t" -WindowStyle Hidden
Start-Sleep -Seconds 1
}
Write-Host "=== 1. connect form (V4 entry state) ==="
[void](Adb-Shell @("am","force-stop","com.verify.remote"))
[void](Adb-Shell @("am","start","-n","com.verify.remote/.MainActivity"))
Start-Sleep -Seconds 3
$ui = Get-Ui
if ($ui -notmatch "android\.widget\.EditText") { throw "form fields missing" }
Shot "4b-form.png"
Write-Host "=== 2. failure path (V5): unreachable host via intent ==="
[void](Adb-Shell @("am","force-stop","com.verify.remote"))
[void](Adb-Shell @("am","start","-n","com.verify.remote/.MainActivity",
"--es","host","192.168.1.199","--ei","port","47800","--es","code","t"))
# Measured 2026-10-30: an unreachable host returns to the form with a concrete
# reason after ~32s (quinn's default connect timeout). Budget 50s, not 30s -
# the earlier 30s budget made this step look like a failure when it was only
# still inside the timeout. UX follow-up (logged in the result record): during
# those 30s the panel says only "connecting" with no cancel affordance.
$deadline = (Get-Date).AddSeconds(50)
$err = ""
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 1000
$ui = Get-Ui
if ($ui -match "android\.widget\.EditText") {
$shown = [regex]::Matches($ui, 'text="([^"]+)"') | ForEach-Object { $_.Groups[1].Value }
$err = $shown -join " | "
break
}
}
if ($err) { Shot "4b-v5-error.png" } else { throw "V5: never returned to form with an error" }
Write-Host " error state visible: yes"
Write-Host "=== 3. live session (V4 via intent) ==="
[void](Adb-Shell @("am","force-stop","com.verify.remote"))
[void](Adb-Shell @("am","start","-n","com.verify.remote/.MainActivity",
"--es","host",$phoneIp,"--ei","port","47800","--es","code","t"))
$ui = Wait-Live 25
if (-not $ui) { throw "session never became live" }
Shot "4b-connected-stream.png"
Write-Host " $([regex]::Match($ui,'text="([^"]*RTT=[^"]*)"').Groups[1].Value)"
Write-Host "=== 4. keyboard shown (real IME bound to our InputConnection) ==="
$btns = [regex]::Matches($ui, '<node[^>]*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
if ($btns.Count -lt 1) { throw "no keyboard button" }
$b0 = $btns[0]
$kx = [int](([int]$b0.Groups[1].Value + [int]$b0.Groups[3].Value) / 2)
$ky = [int](([int]$b0.Groups[2].Value + [int]$b0.Groups[4].Value) / 2)
[void](Adb-Shell @("input","tap","$kx","$ky"))
Start-Sleep -Seconds 3
$ime = (Adb-Shell @("dumpsys","input_method")).out
if ($ime -notmatch "mInputShown=true") { throw "IME not shown" }
if ($ime -notmatch "mHaveConnection=true") { throw "no InputConnection bound" }
Shot "4b-ime-shown.png"
Write-Host "=== 5. IME commit reaches the pipeline (diagnostic shows commit) ==="
[void](Adb-Shell @("input","text","hello"))
Start-Sleep -Milliseconds 1200
[void](Adb-Shell @("input","keyevent","62")) # SPACE: accept any pending candidate
Start-Sleep -Seconds 2
$ui = Get-Ui
$diag = [regex]::Match($ui, 'text="([^"]*RTT=[^"]*)"').Groups[1].Value
Write-Host " phone status: $diag"
Shot "4b-ime-committed.png"
Write-Host "=== 6. disconnect (back key) ==="
[void](Adb-Shell @("input","keyevent","4")) # dismiss keyboard
Start-Sleep -Milliseconds 800
[void](Adb-Shell @("input","keyevent","4")) # disconnect
Start-Sleep -Seconds 2
$ui = Get-Ui
if ($ui -match "RTT=") { throw "still connected after two BACK presses" }
Shot "4b-disconnected.png"
Write-Host "=== CAPTURE COMPLETE ==="
Get-ChildItem $ev -Filter "4b-*.png" | ForEach-Object {
$b = [System.IO.File]::ReadAllBytes($_.FullName)
$sig = ($b[0..3] | ForEach-Object { $_.ToString("X2") }) -join " "
"{0,-28} {1,9} {2}" -f $_.Name, $b.Length, $sig
}