# 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, ']*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 }