|
|
|
|
@ -0,0 +1,432 @@
|
|
|
|
|
# 3a connect-lifecycle proof (phone side): U8 timeout/cancel, error classes, BACK-cancel, HUD.
|
|
|
|
|
#
|
|
|
|
|
# ASCII-only source (PS 5.1 decodes BOM-less .ps1 as GBK; see personal lessons).
|
|
|
|
|
# Expected Chinese UI strings are matched as CODE-POINT fragments extracted from
|
|
|
|
|
# the Kotlin sources at authoring time (R-8: assert the actual value, and print
|
|
|
|
|
# what the parser really read so an empty result cannot pass silently).
|
|
|
|
|
#
|
|
|
|
|
# Judgements (V-A1 rewritten per loop-3-plan D3 redial ruling, user 2026-10-31):
|
|
|
|
|
# V-A2a wrong code -> AUTH class text + original-error detail line
|
|
|
|
|
# V-A1a unresolvable host -> RESOLVE text fast (<3s; Resolve is NOT retryable)
|
|
|
|
|
# V-A1b blackhole IP -> connecting shows CANCEL; tap -> actionable form <1.5s
|
|
|
|
|
# V-A4 BACK while connecting -> cancel to form, activity NOT finished
|
|
|
|
|
# V-A5 live session -> HUD has fps= and no coordinate noise (norm=/view=)
|
|
|
|
|
#
|
|
|
|
|
# The IME is disabled for the whole run (form fields then receive raw key events,
|
|
|
|
|
# no soft keyboard covering the button; same pattern as phone_chain_proof step 6)
|
|
|
|
|
# and restored in `finally`.
|
|
|
|
|
#
|
|
|
|
|
# Prereqs: vivo device online via adb, 3a APK installed, v3 agent build present.
|
|
|
|
|
# powershell -ExecutionPolicy Bypass -File tools\connect_lifecycle_proof.ps1 -Host_ 192.168.1.110
|
|
|
|
|
param(
|
|
|
|
|
[string]$Host_ = "192.168.1.110",
|
|
|
|
|
[int]$Port = 47800,
|
|
|
|
|
[string]$Code = "t",
|
|
|
|
|
[string]$AgentExe = ""
|
|
|
|
|
)
|
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
$repo = (Resolve-Path "$PSScriptRoot\..").Path
|
|
|
|
|
$adb = "D:\working\android\sdk\platform-tools\adb.exe"
|
|
|
|
|
$ev = Join-Path (Split-Path $repo -Parent) "evidence\3a"
|
|
|
|
|
New-Item -ItemType Directory -Force -Path $ev | Out-Null
|
|
|
|
|
|
|
|
|
|
# ---- code-point fragments extracted from Kotlin sources THIS authoring session
|
|
|
|
|
# (grep-verified present in ConnectErrorText.kt / MainActivity.kt): ----
|
|
|
|
|
# NB: build with `-join (...)`, NOT `[string](a,b)` - the latter stringifies the
|
|
|
|
|
# array with spaces ("连 接", len 3) and silently never matches (measured
|
|
|
|
|
# 2026-10-31; visibleTexts comparison then finds 'no button' for a button that
|
|
|
|
|
# is plainly on screen).
|
|
|
|
|
$F_RESOLVE = -join ([char]0x65E0,[char]0x6CD5,[char]0x89E3,[char]0x6790,[char]0x8FD9,[char]0x4E2A,[char]0x5730,[char]0x5740) # "cannot resolve this address"
|
|
|
|
|
$F_AUTH = -join ([char]0x9A8C,[char]0x8BC1,[char]0x7801,[char]0x9519,[char]0x8BEF) # "verification code wrong"
|
|
|
|
|
$F_CANCELED = -join ([char]0x8FDE,[char]0x63A5,[char]0x5DF2,[char]0x53D6,[char]0x6D88) # "connection cancelled"
|
|
|
|
|
$F_RECONNECT = -join ([char]0x91CD,[char]0x65B0,[char]0x8FDE,[char]0x63A5) # "reconnect"
|
|
|
|
|
$F_CANCEL = -join ([char]0x53D6,[char]0x6D88) # "cancel"
|
|
|
|
|
$F_CONNECT = -join ([char]0x8FDE,[char]0x63A5) # "connect"
|
|
|
|
|
$F_KILLMSG = -join ([char]0x8FDE,[char]0x63A5,[char]0x5DF2,[char]0x65AD,[char]0x5F00) # "connection lost"
|
|
|
|
|
|
|
|
|
|
function Adb {
|
|
|
|
|
param([Parameter(Mandatory)][string[]]$AdbArgs)
|
|
|
|
|
$old = $ErrorActionPreference; $ErrorActionPreference = "Continue"
|
|
|
|
|
try { $out = & $adb @AdbArgs 2>$null; @{ out = ($out -join "`n"); code = $LASTEXITCODE } }
|
|
|
|
|
finally { $ErrorActionPreference = $old }
|
|
|
|
|
}
|
|
|
|
|
# NB: the concat must be parenthesized. `Adb @("shell") + $Cmd` parses `+` as a
|
|
|
|
|
# literal extra argument (measured 2026-10-31: every shell call silently ran
|
|
|
|
|
# `adb shell + ...` on the device, returning empty output - the form 'vanished'
|
|
|
|
|
# because uiautomator never actually ran).
|
|
|
|
|
function AdbShell {
|
|
|
|
|
param([string[]]$Cmd)
|
|
|
|
|
(Adb -AdbArgs (@("shell") + $Cmd)).out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-UiText {
|
|
|
|
|
$d = AdbShell @("uiautomator","dump","/sdcard/cl.xml")
|
|
|
|
|
$r = Adb @("pull","/sdcard/cl.xml","$env:TEMP\cl.xml")
|
|
|
|
|
if ($r.code -ne 0 -or -not (Test-Path "$env:TEMP\cl.xml")) {
|
|
|
|
|
Write-Host " [ui] dump=[$($d -replace "`n",' ')] pull_rc=$($r.code) -> empty"
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
$txt = [System.IO.File]::ReadAllText("$env:TEMP\cl.xml", [System.Text.Encoding]::UTF8)
|
|
|
|
|
if ($txt.Length -lt 200) {
|
|
|
|
|
# dump can 'succeed' yet hand back a stale stub; show what we really read
|
|
|
|
|
Write-Host " [ui] short xml ($($txt.Length) chars): $($txt.Substring(0,[Math]::Min(120,$txt.Length)))"
|
|
|
|
|
}
|
|
|
|
|
return $txt
|
|
|
|
|
}
|
|
|
|
|
function VisibleTexts([string]$xml) {
|
|
|
|
|
[regex]::Matches($xml,'text="([^"]+)"') | ForEach-Object { [System.Net.WebUtility]::HtmlDecode($_.Groups[1].Value) }
|
|
|
|
|
}
|
|
|
|
|
function FormFields([string]$xml) {
|
|
|
|
|
[regex]::Matches($xml,'<node[^>]*class="android\.widget\.EditText"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
|
|
|
|
|
}
|
|
|
|
|
function Center($m) {
|
|
|
|
|
# NB: cast to [int] BEFORE adding. A regex group .Value is a STRING and PS
|
|
|
|
|
# "60" + "1020" CONCATENATES ("601020") -> /2 then lands the tap ~300000px
|
|
|
|
|
# off-screen, silently. Measured 2026-10-31; phone_chain_proof's Center is
|
|
|
|
|
# the proven form - copy it, don't re-derive.
|
|
|
|
|
$a=[int]$m.Groups[1].Value; $b=[int]$m.Groups[2].Value
|
|
|
|
|
$c=[int]$m.Groups[3].Value; $d=[int]$m.Groups[4].Value
|
|
|
|
|
@([int](($a+$c)/2), [int](($b+$d)/2))
|
|
|
|
|
}
|
|
|
|
|
function ButtonCenter([string]$xml, [string]$want) {
|
|
|
|
|
foreach ($n in [regex]::Matches($xml,'<node[^>]*class="android\.widget\.Button"[^>]*>')) {
|
|
|
|
|
$t = [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value)
|
|
|
|
|
if ($t -eq $want) {
|
|
|
|
|
$b = [regex]::Match($n.Value,'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
|
|
|
|
|
if ($b.Success) {
|
|
|
|
|
$x1=[int]$b.Groups[1].Value; $y1=[int]$b.Groups[2].Value
|
|
|
|
|
$x2=[int]$b.Groups[3].Value; $y2=[int]$b.Groups[4].Value
|
|
|
|
|
return @([int](($x1+$x2)/2), [int](($y1+$y2)/2))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Physical-key input with the IME disabled. Key codes from javap against
|
|
|
|
|
# android-34 (same table phone_chain_proof uses): KEYCODE_0=7..9=16,
|
|
|
|
|
# KEYCODE_A=29..Z=54 contiguous (T=48 verified there), PERIOD=56.
|
|
|
|
|
# NB1: [int]'A' converts the STRING and throws; letter anchor is [int][char]'A'.
|
|
|
|
|
# NB2: [int][string]'0' == 0 (parse), so digits map 7+digit (an earlier draft
|
|
|
|
|
# got this wrong twice: `-48`, then MINUS=14 - which is KEYCODE_7!).
|
|
|
|
|
# MINUS (69) is deliberately NOT supported: throw instead of typing a wrong
|
|
|
|
|
# key (measured 2026-10-31: a wrong MINUS mapping silently typed '7' into
|
|
|
|
|
# the code field and corrupted the whole fill sequence).
|
|
|
|
|
function TypeAscii([string]$s) {
|
|
|
|
|
foreach ($ch in $s.ToUpper().ToCharArray()) {
|
|
|
|
|
$k = if ($ch -ge '0' -and $ch -le '9') { 7 + [int][string]$ch }
|
|
|
|
|
elseif ($ch -ge 'A' -and $ch -le 'Z') { 29 + ([int]$ch - [int][char]'A') }
|
|
|
|
|
elseif ($ch -eq '.') { 56 }
|
|
|
|
|
else { throw "unsupported char: $ch" }
|
|
|
|
|
[void](AdbShell @("input","keyevent","$k"))
|
|
|
|
|
# 35ms lost a digit on the live device (port arrived as "4780");
|
|
|
|
|
# 70ms gap + per-field verify-and-retry below is the honest fix.
|
|
|
|
|
Start-Sleep -Milliseconds 70
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# which field (0..2) currently holds focus? "" if none.
|
|
|
|
|
function FocusedIndex([string]$xml) {
|
|
|
|
|
$i = -1
|
|
|
|
|
foreach ($n in [regex]::Matches($xml,'<node[^>]*>')) {
|
|
|
|
|
if ($n.Value -match 'class="android\.widget\.EditText"') {
|
|
|
|
|
$i++
|
|
|
|
|
if ($n.Value -match 'focused="true"') { return $i }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Tap a field's center and VERIFY focus landed there (2b N-series discipline:
|
|
|
|
|
# automation must assert its own preconditions - adb taps are not guaranteed).
|
|
|
|
|
function Focus-Field($c, [int]$wantIdx) {
|
|
|
|
|
for ($try=0; $try -lt 3; $try++) {
|
|
|
|
|
[void](AdbShell @("input","tap","$($c[0])","$($c[1])"))
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(1.5)
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 250
|
|
|
|
|
if ((FocusedIndex (Get-UiText)) -eq $wantIdx) { return }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw "tap did not focus field $wantIdx after 3 tries (focused=$(FocusedIndex (Get-UiText)))"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Clear-Field($c, [int]$idx) {
|
|
|
|
|
Focus-Field $c $idx
|
|
|
|
|
for ($i=0; $i -lt 40; $i++) { [void](AdbShell @("input","keyevent","67")) }
|
|
|
|
|
Start-Sleep -Milliseconds 150
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Restart-AppForm {
|
|
|
|
|
# vivo keeps re-sleeping mid-run (measured 2026-10-31: battery-saver + a 7s
|
|
|
|
|
# screen-off timeout re-locked the keyguard between steps, so uiautomator saw
|
|
|
|
|
# the always-on-display clock instead of the app and 'form did not appear').
|
|
|
|
|
# Wake + dismiss is idempotent and keeps the harness unattended-safe; if the
|
|
|
|
|
# device is PIN-locked a bare wake is not enough and this will fail loudly.
|
|
|
|
|
[void](AdbShell @("input","keyevent","224")) # WAKEUP
|
|
|
|
|
Start-Sleep -Milliseconds 300
|
|
|
|
|
[void](AdbShell @("wm","dismiss-keyguard"))
|
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
|
[void](AdbShell @("am","force-stop","com.verify.remote"))
|
|
|
|
|
Start-Sleep -Milliseconds 600
|
|
|
|
|
[void](AdbShell @("am","start","-n","com.verify.remote/.MainActivity")) # no extras -> form
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(8)
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
$x = Get-UiText
|
|
|
|
|
if ((FormFields $x).Count -ge 3) { return $x }
|
|
|
|
|
Start-Sleep -Milliseconds 300
|
|
|
|
|
}
|
|
|
|
|
$diag = (VisibleTexts (Get-UiText)) -join " || "
|
|
|
|
|
throw "connect form did not appear (expected >=3 EditText). screen shows: [$diag]"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Fill host/port/code into the 3 form fields, asserting every precondition
|
|
|
|
|
# (R-8 + 2026-10-31 lesson: an adb tap does NOT guarantee focus - one failed run
|
|
|
|
|
# left all three type-ins in field 0 and the single 'vals[0] -ne host' check
|
|
|
|
|
# caught it only as a corruption symptom). Now: focus is verified per field, and
|
|
|
|
|
# every field's final text must equal its intended value. Returns tap time.
|
|
|
|
|
function FillAndConnect([string]$xml, [string]$host_, [string]$port_, [string]$code_) {
|
|
|
|
|
$f = FormFields $xml
|
|
|
|
|
if ($f.Count -lt 3) { throw "form has $($f.Count) fields" }
|
|
|
|
|
# NB: do NOT gather the three centers into @(a,b,c) - PowerShell array
|
|
|
|
|
# literals FLATTEN nested arrays, silently turning 3 coord-pairs into 6 ints;
|
|
|
|
|
# $cs[0] then holds only the x of field 0, every tap lands at a bogus point,
|
|
|
|
|
# focus never moves (measured 2026-10-31: 'focused=-1' was the honest symptom).
|
|
|
|
|
# Pass each call's array result directly as ONE argument.
|
|
|
|
|
Clear-Field (Center $f[0]) 0; TypeAscii $host_
|
|
|
|
|
Clear-Field (Center $f[1]) 1; TypeAscii $port_
|
|
|
|
|
Clear-Field (Center $f[2]) 2; TypeAscii $code_
|
|
|
|
|
# Verify ALL THREE fields hold their intended value; on mismatch, retype the
|
|
|
|
|
# offending field and re-check (max 2 retries) - a dropped key event on the
|
|
|
|
|
# live device is a known flake (measured: port typed as "4780"), and the
|
|
|
|
|
# final judgement must still be 'equals expected', never 'close enough'.
|
|
|
|
|
$expect = @($host_, $port_, $code_)
|
|
|
|
|
for ($retry=0; $retry -le 2; $retry++) {
|
|
|
|
|
$x2 = Get-UiText
|
|
|
|
|
$vals = @()
|
|
|
|
|
foreach ($n in [regex]::Matches($x2,'<node[^>]*>')) {
|
|
|
|
|
if ($n.Value -match 'class="android\.widget\.EditText"') {
|
|
|
|
|
$vals += [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Write-Host " form now: $($vals -join ' | ') (try $retry)"
|
|
|
|
|
if ($vals.Count -lt 3) { throw "expected 3 fields after typing, got $($vals.Count)" }
|
|
|
|
|
$bad = -1
|
|
|
|
|
for ($i=0; $i -lt 3; $i++) { if ($vals[$i] -ne $expect[$i]) { $bad = $i; break } }
|
|
|
|
|
if ($bad -ge 0) {
|
|
|
|
|
$f = FormFields $x2
|
|
|
|
|
if ($f.Count -lt 3) { throw "form lost fields during retry" }
|
|
|
|
|
Clear-Field (Center $f[$bad]) $bad
|
|
|
|
|
TypeAscii $expect[$bad]
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
if ($vals[0] -ne $host_) { throw "form input FAIL: host field is [$($vals[0])], expected [$host_]" }
|
|
|
|
|
if ($vals[1] -ne $port_) { throw "form input FAIL: port field is [$($vals[1])], expected [$port_]" }
|
|
|
|
|
if ($vals[2] -ne $code_) { throw "form input FAIL: code field is [$($vals[2])], expected [$code_]" }
|
|
|
|
|
$btn = $null
|
|
|
|
|
foreach ($want in @($F_CONNECT, $F_RECONNECT)) { $btn = ButtonCenter $x2 $want; if ($btn) { break } }
|
|
|
|
|
if (-not $btn) { throw "no primary connect/reconnect button found" }
|
|
|
|
|
$t = Get-Date
|
|
|
|
|
[void](AdbShell @("input","tap","$($btn[0])","$($btn[1])"))
|
|
|
|
|
return $t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Wait-Connecting([string]$blackhole, [double]$seconds) {
|
|
|
|
|
# connecting state = remote panel up, bottom bar shows the CANCEL button
|
|
|
|
|
$deadline = (Get-Date).AddSeconds($seconds)
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
$x = Get-UiText
|
|
|
|
|
if (ButtonCenter $x $F_CANCEL) { return $x }
|
|
|
|
|
Start-Sleep -Milliseconds 250
|
|
|
|
|
}
|
|
|
|
|
throw "never entered connecting state (no CANCEL button within ${seconds}s)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "=== 0. stale-agent guard (R-7) + IME disable (restore in finally) ==="
|
|
|
|
|
if (-not $AgentExe) { $AgentExe = Join-Path $repo "target\release\agent.exe" }
|
|
|
|
|
$ag = @(Get-Process agent -ErrorAction SilentlyContinue)
|
|
|
|
|
if ($ag.Count -gt 0 -and (Test-Path $AgentExe) -and ((Get-Item $AgentExe).LastWriteTime -gt $ag[0].StartTime)) {
|
|
|
|
|
Write-Host " running agent predates current build; restarting"
|
|
|
|
|
Stop-Process -Name agent -Force -ErrorAction SilentlyContinue
|
|
|
|
|
Start-Sleep -Milliseconds 800; $ag = @()
|
|
|
|
|
}
|
|
|
|
|
if ($ag.Count -eq 0) {
|
|
|
|
|
if (-not (Test-Path $AgentExe)) { throw "missing $AgentExe (cargo build --release -p agent)" }
|
|
|
|
|
Start-Process $AgentExe -ArgumentList "--port","$Port","--code",$Code -WindowStyle Hidden | Out-Null
|
|
|
|
|
Start-Sleep -Seconds 1; Write-Host " agent started (v3)"
|
|
|
|
|
} else { Write-Host " agent running and current" }
|
|
|
|
|
|
|
|
|
|
$origIme = ((AdbShell @("settings","get","secure","default_input_method")) -join "").Trim()
|
|
|
|
|
Write-Host " original IME: [$origIme] (N13 discipline: recorded, restored in finally)"
|
|
|
|
|
if ($origIme -and $origIme -ne "null") { [void](AdbShell @("ime","disable",$origIme)) }
|
|
|
|
|
# keep the screen awake for the whole run (measured 2026-10-31: mid-run screen
|
|
|
|
|
# timeout turned every later tap/keyevent into a no-op against the AOD - the
|
|
|
|
|
# 'cancel did not return to form' FAIL was the sleep, not the app; a second
|
|
|
|
|
# probe reproduced it with dump texts showing clock+battery instead of the app)
|
|
|
|
|
[void](AdbShell @("svc","power","stayon","true"))
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
# =================================================================
|
|
|
|
|
Write-Host "=== 1. V-A2a: wrong code -> AUTH class text + detail line ==="
|
|
|
|
|
$x = Restart-AppForm
|
|
|
|
|
[void](FillAndConnect $x $Host_ "$Port" "wrongcode3a")
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(8); $seenAuth = $false; $seenDetail = $false; $shown = ""
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 400
|
|
|
|
|
$shown = ((VisibleTexts (Get-UiText)) -join " || ")
|
|
|
|
|
if ($shown.Contains($F_AUTH)) { $seenAuth = $true }
|
|
|
|
|
if ($shown.Contains("detail:")) { $seenDetail = $true }
|
|
|
|
|
if ($seenAuth) { break }
|
|
|
|
|
}
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a2-auth.png" | Out-Null
|
|
|
|
|
Write-Host " screen: $shown"
|
|
|
|
|
if (-not $seenAuth) { throw "V-A2a FAIL: no AUTH text ('$F_AUTH'); got [$shown]" }
|
|
|
|
|
if (-not $seenDetail) { throw "V-A2a FAIL: AUTH shown but no 'detail:' original-error line; got [$shown]" }
|
|
|
|
|
Write-Host " PASS: wrong code -> '$F_AUTH' + detail line"
|
|
|
|
|
|
|
|
|
|
# =================================================================
|
|
|
|
|
Write-Host "=== 2. V-A1a: unresolvable host -> RESOLVE fast (NOT redialed) ==="
|
|
|
|
|
$x = Restart-AppForm
|
|
|
|
|
$t = FillAndConnect $x "999.999.999.999" "$Port" $Code
|
|
|
|
|
$deadline = $t.AddSeconds(3); $seen = $false; $shown = ""; $elapsed = -1
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 200
|
|
|
|
|
$shown = ((VisibleTexts (Get-UiText)) -join " || ")
|
|
|
|
|
if ($shown.Contains($F_RESOLVE)) { $seen = $true; $elapsed = ((Get-Date) - $t).TotalSeconds; break }
|
|
|
|
|
}
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a1-resolve.png" | Out-Null
|
|
|
|
|
Write-Host " screen: $shown (after ${elapsed}s)"
|
|
|
|
|
if (-not $seen) { throw "V-A1a FAIL: no RESOLVE text ('$F_RESOLVE') within 3s; got [$shown]" }
|
|
|
|
|
Write-Host " PASS: unresolvable host -> '$F_RESOLVE' in ${elapsed}s (<3s proves no redial)"
|
|
|
|
|
|
|
|
|
|
# =================================================================
|
|
|
|
|
Write-Host "=== 3. V-A1b: blackhole IP -> CANCEL tap -> 'cancelled' form ==="
|
|
|
|
|
# Intent path (2026-10-31 lesson): with the form path, taps during connecting
|
|
|
|
|
# proved unreliable in THIS app - the EditText still holds focus when the
|
|
|
|
|
# panel swaps (the form's own click chain leaves focus where it was), and the
|
|
|
|
|
# first tap only defocuses: manual form-path probe lost the tap 4 rounds in a
|
|
|
|
|
# row, while the intent path (no EditText ever focused) took ONE tap and
|
|
|
|
|
# returned '连接已取消' instantly. Intent auto-connect reaches the very same
|
|
|
|
|
# connecting state (same startConnect code), so it isolates the product
|
|
|
|
|
# behaviour from keyboard/focus noise.
|
|
|
|
|
[void](AdbShell @("input","keyevent","224"))
|
|
|
|
|
[void](AdbShell @("wm","dismiss-keyguard"))
|
|
|
|
|
[void](AdbShell @("am","force-stop","com.verify.remote"))
|
|
|
|
|
Start-Sleep -Milliseconds 600
|
|
|
|
|
[void](AdbShell @("am","start","-n","com.verify.remote/.MainActivity",
|
|
|
|
|
"--es","host","192.0.2.1","--ei","port","$Port","--es","code",$Code))
|
|
|
|
|
$xc = Wait-Connecting "192.0.2.1" 6
|
|
|
|
|
$cancel = ButtonCenter $xc $F_CANCEL
|
|
|
|
|
if (-not $cancel) { throw "V-A1b FAIL: no CANCEL button in intent connecting state" }
|
|
|
|
|
$tc = Get-Date
|
|
|
|
|
[void](AdbShell @("input","tap","$($cancel[0])","$($cancel[1])"))
|
|
|
|
|
$deadline = $tc.AddSeconds(3); $back = $false; $shown = ""
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 250
|
|
|
|
|
$x2 = Get-UiText
|
|
|
|
|
$shown = ((VisibleTexts $x2) -join " || ")
|
|
|
|
|
# judgement = the CANCEL-triggered text specifically (F_CANCELED), not
|
|
|
|
|
# merely 'back at the form' - the 8s budget would also return a form
|
|
|
|
|
# (with the timeout text) and that must not masquerade as a cancel PASS.
|
|
|
|
|
if ((FormFields $x2).Count -ge 3 -and $shown.Contains($F_CANCELED)) { $back = $true; break }
|
|
|
|
|
}
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a1-cancel.png" | Out-Null
|
|
|
|
|
Write-Host " after cancel: $shown"
|
|
|
|
|
if (-not $back) { throw "V-A1b FAIL: cancel tap did not surface '$F_CANCELED' within 3s; got [$shown]" }
|
|
|
|
|
$ms = [int]((Get-Date).Subtract($tc).TotalMilliseconds)
|
|
|
|
|
Write-Host " PASS: connecting -> CANCEL -> '$F_CANCELED' form in ${ms}ms"
|
|
|
|
|
|
|
|
|
|
# =================================================================
|
|
|
|
|
Write-Host "=== 4. V-A4: BACK while connecting -> cancel, activity survives ==="
|
|
|
|
|
# same intent-path rationale as step 3 (BACK also needs the app window to
|
|
|
|
|
# own input focus; a focused EditText would eat the first gesture instead)
|
|
|
|
|
[void](AdbShell @("input","keyevent","224"))
|
|
|
|
|
[void](AdbShell @("wm","dismiss-keyguard"))
|
|
|
|
|
[void](AdbShell @("am","force-stop","com.verify.remote"))
|
|
|
|
|
Start-Sleep -Milliseconds 600
|
|
|
|
|
[void](AdbShell @("am","start","-n","com.verify.remote/.MainActivity",
|
|
|
|
|
"--es","host","192.0.2.1","--ei","port","$Port","--es","code",$Code))
|
|
|
|
|
[void](Wait-Connecting "192.0.2.1" 6)
|
|
|
|
|
[void](AdbShell @("input","keyevent","4")) # BACK
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(3); $atForm = $false; $shown = ""
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 250
|
|
|
|
|
$x2 = Get-UiText
|
|
|
|
|
$shown = ((VisibleTexts $x2) -join " || ")
|
|
|
|
|
if ((FormFields $x2).Count -ge 3 -and $shown.Contains($F_CANCELED)) { $atForm = $true; break }
|
|
|
|
|
}
|
|
|
|
|
$focusLine = ((AdbShell @("dumpsys","window")) -join "`n")
|
|
|
|
|
$alive = $focusLine -match "com\.verify\.remote"
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a4-back.png" | Out-Null
|
|
|
|
|
Write-Host " after BACK: activityAlive=$alive atForm=$atForm"
|
|
|
|
|
Write-Host " screen: $shown"
|
|
|
|
|
if (-not $alive) { throw "V-A4 FAIL: BACK during connect finished the activity (old super.onKeyDown regression)" }
|
|
|
|
|
if (-not $atForm) { throw "V-A4 FAIL: BACK during connect did not surface '$F_CANCELED' on the form" }
|
|
|
|
|
Write-Host " PASS: BACK during connect -> cancel + form, activity kept"
|
|
|
|
|
|
|
|
|
|
# =================================================================
|
|
|
|
|
Write-Host "=== 5. V-A5: live session HUD: fps= present, no coordinate noise ==="
|
|
|
|
|
$x = Restart-AppForm
|
|
|
|
|
[void](FillAndConnect $x $Host_ "$Port" $Code)
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(12); $live = $false; $hud = ""
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
|
$m = [regex]::Match((Get-UiText),'text="([^"]*RTT=[^"]*)"')
|
|
|
|
|
if ($m.Success) { $hud = [System.Net.WebUtility]::HtmlDecode($m.Groups[1].Value); $live = $true; break }
|
|
|
|
|
}
|
|
|
|
|
$hasFps = $hud.Contains("fps=")
|
|
|
|
|
$noNoise = -not ($hud.Contains("norm=") -or $hud.Contains("view="))
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a5-hud.png" | Out-Null
|
|
|
|
|
Write-Host " HUD: $hud"
|
|
|
|
|
if (-not $live) { throw "V-A5 FAIL: session never reached live state" }
|
|
|
|
|
if (-not $hasFps) { throw "V-A5 FAIL: HUD missing fps= ; got [$hud]" }
|
|
|
|
|
if (-not $noNoise) { throw "V-A5 FAIL: HUD carries coordinate noise (norm=/view=)" }
|
|
|
|
|
Write-Host " PASS: live HUD shows fps, no coordinate noise"
|
|
|
|
|
|
|
|
|
|
Write-Host "=== 5b. V-A3 re-check: kill agent -> 'lost' form within ~16s ==="
|
|
|
|
|
Stop-Process -Name agent -Force -ErrorAction SilentlyContinue
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(16); $lost = $false; $shown = ""
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
|
$x3 = Get-UiText
|
|
|
|
|
$shown = ((VisibleTexts $x3) -join " || ")
|
|
|
|
|
if ($shown.Contains($F_KILLMSG) -and (FormFields $x3).Count -ge 3) { $lost = $true; break }
|
|
|
|
|
}
|
|
|
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-a3-lost.png" | Out-Null
|
|
|
|
|
Write-Host " after kill: $shown"
|
|
|
|
|
if (-not $lost) { throw "V-A3 FAIL: kill did not surface '$F_KILLMSG' form within 16s" }
|
|
|
|
|
Write-Host " PASS: agent killed -> lost-session surfaces on form (idle-timeout bound)"
|
|
|
|
|
|
|
|
|
|
Write-Host "=== 6. reconnect to a fresh agent (self-heal path) ==="
|
|
|
|
|
Start-Process $AgentExe -ArgumentList "--port","$Port","--code",$Code -WindowStyle Hidden | Out-Null
|
|
|
|
|
Start-Sleep -Seconds 2
|
|
|
|
|
$x = Get-UiText
|
|
|
|
|
$btn = ButtonCenter $x $F_RECONNECT
|
|
|
|
|
if (-not $btn) { $x = Restart-AppForm; $btn = $null }
|
|
|
|
|
if ($btn) {
|
|
|
|
|
[void](AdbShell @("input","tap","$($btn[0])","$($btn[1])"))
|
|
|
|
|
$deadline = (Get-Date).AddSeconds(14); $live2 = $false
|
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
|
if ((Get-UiText) -match "RTT=") { $live2 = $true; break }
|
|
|
|
|
}
|
|
|
|
|
if (-not $live2) { throw "step 6 FAIL: reconnect button did not reach a live session" }
|
|
|
|
|
Write-Host " PASS: one-tap reconnect after agent restart works"
|
|
|
|
|
[void](AdbShell @("input","keyevent","4")) # disconnect, free the slot
|
|
|
|
|
Start-Sleep -Milliseconds 600
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host " (no live reconnect path exercised this run - covered by manual run)"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
[void](AdbShell @("svc","power","stayon","false")) # undo the keep-awake above
|
|
|
|
|
if ($origIme -and $origIme -ne "null") {
|
|
|
|
|
[void](AdbShell @("ime","enable",$origIme))
|
|
|
|
|
[void](AdbShell @("ime","set",$origIme))
|
|
|
|
|
Write-Host " IME restored: $(((AdbShell @('settings','get','secure','default_input_method')) -join '').Trim())"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "=== CONNECT LIFECYCLE PROOF COMPLETE ==="
|