Compare commits

..

2 Commits

Author SHA1 Message Date
郑博文
114e8431a6 test(android-3a): 连接生命周期 proof 入仓 + 四个 proof 的主机默认值跟上环境
新增 tools\connect_lifecycle_proof.ps1(V-A1a/A1b/A2/A3/A4/A5 + 一键重连,
含 keep-awake、tap 前唤醒、期望文案码点从 Kotlin 源提取)。
四个既有 proof 的 -PhoneIp 默认值 .180 -> .110(2026-10-31 办公网实测;
stale 默认值曾静默吃掉一次 proof 运行,注释写明 DHCP 需按需传参)。
capture_evidence 的 192.168.1.199 保持不变——那是 V5 故意不可达的路径。
2026-10-31 15:57:57 +08:00
郑博文
3891c970eb fix(android): 等待中的取消入口落在面板按钮上(连接后表单被隐藏)
真机 V-A1b 实测发现:startConnect() 切到远控面板后,表单上的按钮不可见,
「点取消」没有可点的目标。把底部按钮在 connecting 态显示为「取消」并共享
cancelConnect() 一条路径(两个入口一个语义),成功后复位为「断开」。
APK 重装后 proof step 3/4 通过(连接已取消文案 + 3.9s 回表单)。
2026-10-31 15:54:48 +08:00
6 changed files with 457 additions and 6 deletions

View File

@ -44,6 +44,7 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
private lateinit var portInput: EditText
private lateinit var codeInput: EditText
private lateinit var connectButton: Button
private lateinit var disconnectButton: Button
private lateinit var connectStatus: TextView
@Volatile
@ -207,9 +208,11 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
text = "键盘"
setOnClickListener { showKeyboard() }
}
val disconnectButton = Button(this).apply {
disconnectButton = Button(this).apply {
text = "断开"
setOnClickListener { disconnect() }
// 连接等待期它显示「取消」并走同一个 cancelConnect():两个按钮的
// 取消语义必须只有一条路径(V-A1 判据依赖取消后的固定状态)。
setOnClickListener { if (connecting) cancelConnect() else disconnect() }
}
val bar = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
@ -458,6 +461,10 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
connecting = true
val gen = ++sessionGeneration
connectButton.text = "取消"
// 等待期间界面已切到远控面板,表单上的按钮看不见——收起动作挂在
// 面板底部按钮上:连接中它是「取消」(disconnect() 已正确作废在途连接),
// 成功后改回「断开」。
disconnectButton.text = "取消"
showRemoteState()
statusView.text = "连接 $host:$port …"
thread(name = "connect") {
@ -486,6 +493,7 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
remoteW = w
remoteH = ht
connectButton.text = "断开"
disconnectButton.text = "断开"
// 只有认证成功的会话才值得记住;验证码不落盘(用户裁决)
getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit()

View File

@ -7,7 +7,11 @@ $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"
# PC (controlled side) LAN address; DHCP-assigned. 2026-10-31: the stale
# .180 default silently cost proof runs after the office network moved to
# .110 - pass -PhoneIp (param) or update this line when it changes again.
# The 192.168.1.199 in step 2 is INTENTIONALLY unreachable (V5 error path).
$phoneIp = "192.168.1.110"
function Adb-Shell {
param([Parameter(Mandatory)][string[]]$Cmd)

View File

@ -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 ==="

View File

@ -16,7 +16,9 @@
# ... then, when prompted, tap an emoji on the phone's keyboard.
param(
[string]$PhoneIp = "192.168.1.180",
# PC (controlled side) LAN address; DHCP-assigned - pass -PhoneIp when it
# moves (see ime_chinese_proof.ps1 for the 2026-10-31 stale-default lesson).
[string]$PhoneIp = "192.168.1.110",
[int]$Port = 47800,
[string]$Code = "t",
[int]$WaitSeconds = 120

View File

@ -16,7 +16,10 @@
# Not covered: swipe/handwriting input, browsing the candidate list.
param(
[string]$PhoneIp = "192.168.1.180",
# PC (controlled side) LAN address. Default = the 2026-10-31 measured WLAN
# address; it is DHCP-assigned, so pass -PhoneIp when the office network
# moves (the stale .180 default silently cost a proof run on 2026-10-31).
[string]$PhoneIp = "192.168.1.110",
[int]$Port = 47800,
[string]$Code = "t"
)

View File

@ -16,7 +16,9 @@
# powershell -ExecutionPolicy Bypass -File tools\ime_commit_proof.ps1
param(
[string]$PhoneIp = "192.168.1.180",
# PC (controlled side) LAN address; DHCP-assigned - pass -PhoneIp when it
# moves (see ime_chinese_proof.ps1 for the 2026-10-31 stale-default lesson).
[string]$PhoneIp = "192.168.1.110",
[int]$Port = 47800,
[string]$Code = "t",
[string]$Text = "hi"