新增 landscape_proof.ps1:V-C1~C7 判据(自动横屏 <=2s、旋转零中断、 动画桌面下 >=45fps、控制条自动隐藏、黑边唤出零远端泄漏、四按钮可点、 方向四态循环、表单竖屏)。方向判定只解析 Display: mDisplayId=0 块内 cur=WxH——这台 vivo 无 mRotation= 行,且 dumpsys 里有常驻 1080x2400 的 虚拟屏,匹配首个 cur= 会在已横屏时误报未旋转(探针实测踩过)。 横屏自动隐藏与既有 proof 的判据自冲突(HUD 随控制条 GONE,RTT= 读不到 被误判断线/未连接),三个 proof 统一加「确认隐藏态才走黑边唤醒」: phone_chain_proof、ime_chinese_proof、rel_mouse_phone_proof。 修 rel_mouse_phone_proof 两处: - 2c 基线判据:横屏滑动 768px 的残余相对事件成批延迟落地,旧的「两次 读数相等」被突发间隙骗过(实测假失败 351/1418px)。改为 6 读数沉降 + 4 次采样判漂移 + 绝对泄漏点特征比对。 - step 4 靶窗启动缺 3 个 Mandatory 参数,子进程瞬间失败退出(3b 起一直 如此,右键落在裸屏幕中心,靠近违反 R-11「不得注入他人窗口」)。现按 契约启动并把指针停在自家窗口内。 R-5 同源排查:靶窗结果目录固定路径 + 启动时删自己的 stop.flag,会让 上一轮失败遗留的靶窗永远收不到退休信号、继续抢前台并写脏 received.txt (实测收到 abc\nabc)。5 个 proof 统一改每轮唯一目录(\47512 后缀)。 ime_chinese_proof 的切换键标定失效:设备 IME 已换成搜狗(原为讯飞), 旧坐标 (146,2096) 静默点空。IME 按键视图对 uiautomator 不可见、无法机械 标定,按 R-9 转人工确认,不硬凑自动化。
487 lines
24 KiB
PowerShell
487 lines
24 KiB
PowerShell
# 4a landscape proof: rotation, full-height video, auto-hide controls, regressions.
|
|
#
|
|
# ASCII-only (PS 5.1 reads BOM-less .ps1 as GBK; personal lessons).
|
|
#
|
|
# Judgements (loop-4a-landscape-plan.md section 6):
|
|
# V-C1 connect to 1920x1080 -> app rotates landscape <=2s after live
|
|
# (LOCAL display cur= only: this vivo ROM has a permanent 1080x2400
|
|
# virtual display and prints no mRotation= line - probe lesson 2026-11-01)
|
|
# V-C2 with controls hidden, video view is 16:9 (err <2%) and >=90% screen height
|
|
# V-C3 frame counter strictly increases across rotation (session survives)
|
|
# V-C4 >=45 fps over a 2.5s window with an ACTIVE desktop animator
|
|
# (static desktop is source-limited ~25fps - measuring that would be a
|
|
# category error, probe lesson)
|
|
# V-C5 controls auto-hide ~3s idle; bottom-edge tap wakes them back
|
|
# V-C5b edge-band tap does NOT reach the remote (PC cursor delta <=1px)
|
|
# V-C5c all four buttons present after wake; mode tap changes and restores
|
|
# V-C6 form page is portrait
|
|
# V-C7 orientation button cycles AUTO->L->P->SYS->AUTO, display follows
|
|
#
|
|
# powershell -ExecutionPolicy Bypass -File tools\landscape_proof.ps1 -Host_ 192.168.1.112
|
|
param(
|
|
[string]$Host_ = "192.168.1.112",
|
|
[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\4a"
|
|
New-Item -ItemType Directory -Force -Path $ev | Out-Null
|
|
|
|
# ---- Chinese fragments as code points (R-8) ----
|
|
$F_ORIENT = -join ([char]0x65B9,[char]0x5411,[char]0x3A) # "fangxiang:" (orientation:)
|
|
$F_AUTO = $F_ORIENT + -join ([char]0x81EA,[char]0x52A8) # label: orientation AUTO
|
|
$F_LAND = $F_ORIENT + -join ([char]0x6A2A,[char]0x5C4F) # label: orientation LANDSCAPE
|
|
$F_PORT = $F_ORIENT + -join ([char]0x7AD6,[char]0x5C4F) # label: orientation PORTRAIT
|
|
$F_SYS = $F_ORIENT + -join ([char]0x7CFB,[char]0x7EDF) # label: orientation SYSTEM
|
|
$F_CONNECT = -join ([char]0x8FDE,[char]0x63A5) # label: connect
|
|
$F_RECONNECT = -join ([char]0x91CD,[char]0x65B0,[char]0x8FDE,[char]0x63A5) # label: reconnect
|
|
$F_KB = -join ([char]0x952E,[char]0x76D8) # label: keyboard
|
|
$F_MODE = -join ([char]0x6A21,[char]0x5F0F,[char]0x3A) # "moshi:" (mode:)
|
|
$F_DISC = -join ([char]0x65AD,[char]0x5F00) # label: disconnect
|
|
|
|
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 }
|
|
}
|
|
function AdbShell { param([string[]]$Cmd) (Adb -AdbArgs (@("shell") + $Cmd)).out }
|
|
|
|
function Get-Ui {
|
|
[void](AdbShell @("uiautomator","dump","/sdcard/ls.xml"))
|
|
$r = Adb -AdbArgs @("pull","/sdcard/ls.xml","$env:TEMP\ls.xml")
|
|
if ($r.code -ne 0 -or -not (Test-Path "$env:TEMP\ls.xml")) { return "" }
|
|
return [System.IO.File]::ReadAllText("$env:TEMP\ls.xml", [System.Text.Encoding]::UTF8)
|
|
}
|
|
# uiautomator intermittently returns an empty/failed dump (measured 2026-11-01:
|
|
# a tap lookup got "" and the null-button tap then crashed with an empty-string
|
|
# argument). Retry until the tree is plausibly real; callers still assert on
|
|
# their own node - this only removes the transient, never masks a real absence.
|
|
function Get-UiRetry([int]$tries=4) {
|
|
$x = ""
|
|
for ($i=0; $i -lt $tries; $i++) {
|
|
$x = Get-Ui
|
|
if ($x.Length -gt 500) { return $x }
|
|
Start-Sleep -Milliseconds 400
|
|
}
|
|
Write-Host " [ui] retry exhausted, last dump len=$($x.Length)"
|
|
return $x
|
|
}
|
|
function VisibleTexts([string]$xml) {
|
|
[regex]::Matches($xml,'text="([^"]+)"') | ForEach-Object { [System.Net.WebUtility]::HtmlDecode($_.Groups[1].Value) }
|
|
}
|
|
function ButtonByName([string]$xml, [string]$want, [bool]$prefix=$false) {
|
|
foreach ($n in [regex]::Matches($xml,'<node[^>]*class="android\.widget\.Button"[^>]*>')) {
|
|
$t = [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value)
|
|
$hit = if ($prefix) { $t.StartsWith($want) } else { $t -eq $want }
|
|
if ($hit) {
|
|
$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 @{ cx=[int](($x1+$x2)/2); cy=[int](($y1+$y2)/2) }
|
|
}
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
function ButtonLabelByPrefix([string]$xml, [string]$prefix) {
|
|
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.StartsWith($prefix)) { return $t }
|
|
}
|
|
return $null
|
|
}
|
|
# 4a race (measured 2026-11-01): each uiautomator dump costs ~2s, comparable to
|
|
# the 3s auto-hide timer - find-then-read sequences straddle expiry and labels
|
|
# come back empty. Every observation goes through the helpers below.
|
|
#
|
|
# CRITICAL SAFETY: the wake tap (screen bottom center) is only consumed by the
|
|
# app's edge band WHEN CONTROLS ARE HIDDEN. If controls are visible, that point
|
|
# lands on the control bar (mode/orientation buttons!) and would fire a real
|
|
# action. So a wake is allowed only after positively proving the hidden state:
|
|
# video View node present AND zero Button nodes.
|
|
function ControlsHidden([string]$ui) {
|
|
$anyBtn = ($ui -match 'class="android\.widget\.Button"')
|
|
$video = ($ui -match 'class="android\.view\.View"')
|
|
return ($video -and -not $anyBtn)
|
|
}
|
|
function Wake-Band {
|
|
# 4a design change (measured 2026-11-01): the wake zone is the LETTERBOX
|
|
# BLACK BAR, not a bottom edge band. With controls hidden the video is
|
|
# full-height, so bottom-center lies INSIDE the picture and would fire a
|
|
# real remote click. x=100 is outside the video in both states.
|
|
$d = LocalDisplay
|
|
[void](AdbShell @("input","tap","100","$([int]($d.h/2))"))
|
|
Start-Sleep -Milliseconds 400
|
|
}
|
|
function Find-VisibleButton([string]$want, [bool]$prefix=$false) {
|
|
for ($try=0; $try -lt 5; $try++) {
|
|
$ui = Get-UiRetry
|
|
$b = ButtonByName $ui $want $prefix
|
|
if ($b) { return $b }
|
|
if (ControlsHidden $ui) { Wake-Band; continue }
|
|
if ($ui.Length -gt 500) { return $null } # real tree, genuine absence
|
|
Start-Sleep -Milliseconds 400 # empty-dump transient
|
|
}
|
|
return $null
|
|
}
|
|
function Tap-VisibleButton([string]$want, [bool]$prefix=$false) {
|
|
$b = Find-VisibleButton $want $prefix
|
|
if (-not $b) { return $null }
|
|
[void](AdbShell @("input","tap","$($b.cx)","$($b.cy)"))
|
|
return $b
|
|
}
|
|
# Label read that survives the hide race: wake only from a proven-hidden state.
|
|
function Read-ButtonLabel([string]$want, [bool]$prefix=$false) {
|
|
for ($try=0; $try -lt 6; $try++) {
|
|
$ui = Get-UiRetry
|
|
$l = ButtonLabelByPrefix $ui $want
|
|
if ($l) { return $l }
|
|
if (ControlsHidden $ui) { Wake-Band; continue }
|
|
if ($ui.Length -gt 500) { return $null }
|
|
Start-Sleep -Milliseconds 400
|
|
}
|
|
return $null
|
|
}
|
|
function FrameCount {
|
|
$m = [regex]::Match((Get-Ui), ([char]0x5E27 + '=(\d+)'))
|
|
if ($m.Success) { return [long]$m.Groups[1].Value }
|
|
return -1
|
|
}
|
|
# 4a self-conflict (measured 2026-11-01): once the controls auto-hide, the HUD
|
|
# TextView is GONE too - uiautomator's tree has NO text nodes and FrameCount
|
|
# returns -1 even though the session is alive (the video View is right there).
|
|
# Wake through the bottom edge band first: that tap is consumed by the app
|
|
# (zero remote events), so it cannot perturb the fps window.
|
|
function WakeControls {
|
|
$d = LocalDisplay
|
|
[void](AdbShell @("input","tap","100","$([int]($d.h/2))"))
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
function FrameCountOrWake {
|
|
$f = FrameCount
|
|
if ($f -ge 0) { return $f }
|
|
WakeControls
|
|
return FrameCount
|
|
}
|
|
function LocalDisplay {
|
|
$d = (AdbShell @("dumpsys","window","displays"))
|
|
$idx = $d.IndexOf("Display: mDisplayId=0")
|
|
if ($idx -lt 0) { return @{ w=0; h=0 } }
|
|
$m = [regex]::Match($d.Substring($idx), 'cur=(\d+)x(\d+)')
|
|
if (-not $m.Success) { return @{ w=0; h=0 } }
|
|
return @{ w=[int]$m.Groups[1].Value; h=[int]$m.Groups[2].Value }
|
|
}
|
|
function Wake {
|
|
[void](AdbShell @("input","keyevent","224"))
|
|
Start-Sleep -Milliseconds 250
|
|
[void](AdbShell @("wm","dismiss-keyguard"))
|
|
Start-Sleep -Milliseconds 350
|
|
}
|
|
function Restart-AppForm {
|
|
Wake
|
|
[void](AdbShell @("am","force-stop","com.verify.remote"))
|
|
Start-Sleep -Milliseconds 600
|
|
[void](AdbShell @("am","start","-n","com.verify.remote/.MainActivity"))
|
|
$deadline = (Get-Date).AddSeconds(8)
|
|
while ((Get-Date) -lt $deadline) {
|
|
$x = Get-Ui
|
|
if (([regex]::Matches($x,'class="android\.widget\.EditText"')).Count -ge 3) { return $x }
|
|
Start-Sleep -Milliseconds 300
|
|
}
|
|
throw "form did not appear: [$((VisibleTexts (Get-Ui)) -join ' || ')]"
|
|
}
|
|
function CenterOf($m) {
|
|
$a=[int]$m.Groups[1].Value; $b=[int]$m.Groups[2].Value
|
|
$c=[int]$m.Groups[3].Value; $e=[int]$m.Groups[4].Value
|
|
@([int](($a+$c)/2), [int](($b+$e)/2))
|
|
}
|
|
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"))
|
|
Start-Sleep -Milliseconds 70
|
|
}
|
|
}
|
|
function ClearField($c) {
|
|
for ($try=0; $try -lt 3; $try++) {
|
|
[void](AdbShell @("input","tap","$($c[0])","$($c[1])"))
|
|
Start-Sleep -Milliseconds 250
|
|
for ($i=0; $i -lt 40; $i++) { [void](AdbShell @("input","keyevent","67")) }
|
|
Start-Sleep -Milliseconds 120
|
|
$fx = Get-Ui
|
|
$focused = $false
|
|
$fi = -1
|
|
foreach ($n in [regex]::Matches($fx,'<node[^>]*>')) {
|
|
if ($n.Value -match 'class="android\.widget\.EditText"') {
|
|
$fi++
|
|
if ($n.Value -match 'focused="true"') { $focused = $true }
|
|
}
|
|
}
|
|
if ($focused) { return }
|
|
}
|
|
throw "field focus failed"
|
|
}
|
|
|
|
# PC-side cursor (V-C5b) + desktop animator (V-C4)
|
|
Add-Type -TypeDefinition @"
|
|
using System; using System.Runtime.InteropServices;
|
|
public class Cur4a {
|
|
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; }
|
|
[DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT p);
|
|
[DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y);
|
|
[DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr c);
|
|
public static void Dpi(){ SetProcessDpiAwarenessContext(new IntPtr(-4)); }
|
|
public static int[] Get(){ POINT p; GetCursorPos(out p); return new int[]{p.X,p.Y}; }
|
|
public static void Set(int x,int y){ SetCursorPos(x,y); }
|
|
}
|
|
"@
|
|
[Cur4a]::Dpi()
|
|
|
|
Write-Host "=== 0. memory precheck (probe lesson: OOM fakes failures) ==="
|
|
$os = Get-CimInstance Win32_OperatingSystem
|
|
$freeGB = $os.FreeVirtualMemory/1MB
|
|
Write-Host (" commit free: {0:N1} GB" -f $freeGB)
|
|
if ($freeGB -lt 0.5) { throw "commit memory critically low - free RAM first (gradle --stop)" }
|
|
|
|
Write-Host "=== 1. stale-agent guard (R-7) ==="
|
|
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 " agent predates build; restarting"
|
|
Stop-Process -Name agent -Force -ErrorAction SilentlyContinue
|
|
Start-Sleep -Milliseconds 800; $ag = @()
|
|
}
|
|
if ($ag.Count -eq 0) {
|
|
Start-Process $AgentExe -ArgumentList "--port","$Port","--code",$Code -WindowStyle Hidden | Out-Null
|
|
Start-Sleep -Seconds 2; Write-Host " agent started"
|
|
} else { Write-Host " agent running and current" }
|
|
|
|
$origIme = ((AdbShell @("settings","get","secure","default_input_method")) -join "").Trim()
|
|
if ($origIme -and $origIme -ne "null") { [void](AdbShell @("ime","disable",$origIme)) }
|
|
[void](AdbShell @("svc","power","stayon","true"))
|
|
|
|
try {
|
|
# =====================================================================
|
|
Write-Host "=== 2. V-C6: form is portrait ==="
|
|
$x = Restart-AppForm
|
|
$d = LocalDisplay
|
|
Write-Host (" form display: {0}x{1}" -f $d.w, $d.h)
|
|
if (-not ($d.w -lt $d.h)) { throw "V-C6 FAIL: form not portrait ($($d.w)x$($d.h))" }
|
|
Write-Host " PASS: form portrait"
|
|
|
|
# =====================================================================
|
|
Write-Host "=== 3. V-C1: connect -> AUTO rotates landscape <=2s after live ==="
|
|
$F_edits = [regex]::Matches($x,'<node[^>]*class="android\.widget\.EditText"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
|
|
ClearField (CenterOf $F_edits[0]); TypeAscii $Host_
|
|
ClearField (CenterOf $F_edits[1]); TypeAscii "$Port"
|
|
ClearField (CenterOf $F_edits[2]); TypeAscii $Code
|
|
# Read back until 3 EditText nodes are actually present: uiautomator
|
|
# intermittently returns a truncated/empty tree (measured 2026-11-01, worse
|
|
# under memory pressure) and a single Get-Ui then reads "form now: " empty
|
|
# and fails the fill check as a false negative.
|
|
$vals = @()
|
|
for ($rd=0; $rd -lt 5; $rd++) {
|
|
$x2 = Get-UiRetry
|
|
$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)
|
|
}
|
|
}
|
|
if ($vals.Count -ge 3) { break }
|
|
Write-Host " form readback got $($vals.Count) fields, retry $rd"
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
Write-Host " form now: $($vals -join ' | ')"
|
|
if ($vals.Count -lt 3) { throw "form fill FAIL: only $($vals.Count) fields after retries" }
|
|
if ($vals[0] -ne $Host_ -or $vals[1] -ne "$Port" -or $vals[2] -ne $Code) { throw "form fill FAIL" }
|
|
$btn = ButtonByName $x2 $F_CONNECT
|
|
if (-not $btn) { $btn = ButtonByName $x2 $F_RECONNECT }
|
|
if (-not $btn) { $btn = Find-VisibleButton $F_CONNECT; if (-not $btn) { $btn = Find-VisibleButton $F_RECONNECT } }
|
|
if (-not $btn) { throw "connect button not found" }
|
|
[void](AdbShell @("input","tap","$($btn.cx)","$($btn.cy)"))
|
|
$deadline = (Get-Date).AddSeconds(20); $live = $false
|
|
while ((Get-Date) -lt $deadline) {
|
|
Start-Sleep -Milliseconds 500
|
|
if ((Get-Ui) -match "RTT=") { $live = $true; break }
|
|
}
|
|
if (-not $live) { throw "never reached live: [$((VisibleTexts (Get-Ui)) -join ' || ')]" }
|
|
$tRot = Get-Date
|
|
$deadline = $tRot.AddSeconds(2); $rotated = $false
|
|
while ((Get-Date) -lt $deadline) {
|
|
$d = LocalDisplay
|
|
if ($d.w -gt $d.h) { $rotated = $true; break }
|
|
Start-Sleep -Milliseconds 150
|
|
}
|
|
$ms = ((Get-Date) - $tRot).TotalMilliseconds
|
|
Write-Host (" display {0}x{1}, landscape within {2:N0}ms of live" -f $d.w,$d.h,$ms)
|
|
if (-not $rotated) { throw "V-C1 FAIL: still portrait 2s after live" }
|
|
Write-Host " PASS: AUTO rotated to landscape"
|
|
|
|
# =====================================================================
|
|
Write-Host "=== 4. V-C3 + V-C4: counter monotonic; >=45fps with animated desktop ==="
|
|
# Static desktop is source-limited (~25fps): the honest window needs motion.
|
|
$anim = Start-Job -ScriptBlock {
|
|
Add-Type -TypeDefinition @"
|
|
using System; using System.Runtime.InteropServices;
|
|
public class An { [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); }
|
|
"@
|
|
$end=(Get-Date).AddSeconds(14); $i=0
|
|
while ((Get-Date) -lt $end) {
|
|
$a=$i*0.18
|
|
[An]::SetCursorPos([int](960+320*[Math]::Cos($a)), [int](540+320*[Math]::Sin($a))) | Out-Null
|
|
Start-Sleep -Milliseconds 12; $i++
|
|
}
|
|
}
|
|
Start-Sleep -Seconds 2 # let the encoder see motion
|
|
$fA = FrameCountOrWake; $ta = Get-Date
|
|
Start-Sleep -Milliseconds 2500
|
|
$fB = FrameCountOrWake; $tb = Get-Date
|
|
$fps = if ($fA -ge 0 -and $fB -gt $fA) { [Math]::Round(($fB-$fA)/(($tb-$ta).TotalSeconds),1) } else { -1 }
|
|
Write-Host " frames $fA -> $fB fps=$fps (animated)"
|
|
if ($fB -le $fA) { throw "V-C3 FAIL: counter stalled across rotation" }
|
|
Write-Host " PASS: V-C3 session survived rotation"
|
|
if ($fps -lt 45) { throw "V-C4 FAIL: $fps fps < 45 with animated desktop" }
|
|
Write-Host " PASS: V-C4 fps >= 45"
|
|
|
|
# =====================================================================
|
|
Write-Host "=== 5. V-C5: wake -> visible -> idle -> hidden ==="
|
|
# R-8 discipline: "button absent" is NOT a valid judgement on its own - an
|
|
# empty/broken dump would pass it trivially (measured 2026-11-01: after the
|
|
# hide, the HUD TextView is GONE too, so the tree has zero text nodes).
|
|
# Positive pair: keyboard button absent WHILE the video View node is present.
|
|
#
|
|
# Sequence is explicit (first run's implicit precondition raced: the fps
|
|
# step's wake expired its 3s timer before we checked, so controls were
|
|
# already hidden - correct product behavior, wrong harness assumption):
|
|
# wake -> assert visible -> idle -> assert hidden.
|
|
WakeControls
|
|
$x5 = Get-Ui
|
|
if (-not (ButtonByName $x5 $F_KB)) { throw "V-C5 FAIL: edge wake did not bring controls back" }
|
|
Write-Host " visible after wake: OK"
|
|
$deadline = (Get-Date).AddSeconds(6); $hidden = $false
|
|
while ((Get-Date) -lt $deadline) {
|
|
Start-Sleep -Milliseconds 600
|
|
$x5 = Get-Ui
|
|
$videoSeen = ($x5 -match 'class="android\.view\.View"')
|
|
$kbGone = -not (ButtonByName $x5 $F_KB)
|
|
if ($videoSeen -and $kbGone) { $hidden = $true; break }
|
|
}
|
|
if (-not $hidden) {
|
|
throw "V-C5 FAIL: no (kb-gone AND video-present) state within 6s; last dump len=$($x5.Length)" }
|
|
Write-Host " PASS: controls auto-hidden (video View still in tree - real hide, not empty dump)"
|
|
|
|
Write-Host "=== 5b. V-C2: video 16:9 and >=90% screen height (controls hidden) ==="
|
|
$ui = Get-UiRetry
|
|
$best = $null; $bestErr = 1e9
|
|
foreach ($n in [regex]::Matches($ui,'<node[^>]*class="android\.view\.View"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')) {
|
|
$x1=[int]$n.Groups[1].Value; $y1=[int]$n.Groups[2].Value
|
|
$x2i=[int]$n.Groups[3].Value; $y2=[int]$n.Groups[4].Value
|
|
$w=$x2i-$x1; $h=$y2-$y1
|
|
if ($w -lt 300 -or $h -lt 100) { continue }
|
|
$err = [Math]::Abs($w/[double]$h - 16.0/9.0)
|
|
if ($err -lt $bestErr) { $bestErr=$err; $best=@{w=$w;h=$h} }
|
|
}
|
|
$d = LocalDisplay
|
|
$hFrac = $best.h / [double]$d.h
|
|
Write-Host (" video {0}x{1} ratio err {2:N4} height {3:N1}% of {4}x{5}" -f `
|
|
$best.w,$best.h,$bestErr,($hFrac*100),$d.w,$d.h)
|
|
if ($bestErr -gt 0.02) { throw "V-C2 FAIL: video not 16:9 (err $bestErr)" }
|
|
if ($hFrac -lt 0.90) { throw ("V-C2 FAIL: video height {0:N1}% < 90%" -f ($hFrac*100)) }
|
|
Write-Host " PASS: full-height 16:9 video (the tiny-video fix)"
|
|
Stop-Job $anim -ErrorAction SilentlyContinue; Remove-Job $anim -Force -ErrorAction SilentlyContinue
|
|
|
|
Write-Host "=== 6. V-C5b: black-bar tap wakes controls WITHOUT touching the remote ==="
|
|
Start-Sleep -Seconds 2 # animator fully stopped
|
|
# The wake zone IS the letterbox black bar now (4a design change), so this
|
|
# is the honest target. Also assert a video-interior tap is NOT consumed as
|
|
# a wake - otherwise the black bar would have eaten real clicks too.
|
|
$pc0 = [Cur4a]::Get()
|
|
[void](AdbShell @("input","tap","100","$([int]($d.h/2))"))
|
|
Start-Sleep -Milliseconds 600
|
|
$pc1 = [Cur4a]::Get()
|
|
$ui = Get-UiRetry
|
|
$kb = ButtonByName $ui $F_KB
|
|
$jump = [Math]::Max([Math]::Abs($pc1[0]-$pc0[0]), [Math]::Abs($pc1[1]-$pc0[1]))
|
|
Write-Host (" black-bar tap: cursor jump=$jump px ; keyboard back: {0}" -f ($null -ne $kb))
|
|
if (-not $kb) { throw "V-C5 FAIL: black-bar tap did not wake controls" }
|
|
if ($jump -gt 1) { throw "V-C5b FAIL: black-bar tap leaked $jump px to remote" }
|
|
Write-Host " PASS: wake works, zero remote leakage"
|
|
|
|
Write-Host "=== 6c. four buttons present; mode tap changes and restores ==="
|
|
$kb2 = Find-VisibleButton $F_KB
|
|
$mode = Find-VisibleButton $F_MODE $true
|
|
$disc = Find-VisibleButton $F_DISC
|
|
$orient = Find-VisibleButton $F_ORIENT $true
|
|
if (-not ($kb2 -and $mode -and $disc -and $orient)) {
|
|
throw ("V-C5 FAIL: missing buttons kb={0} mode={1} disc={2} orient={3}" -f `
|
|
($null -ne $kb2),($null -ne $mode),($null -ne $disc),($null -ne $orient)) }
|
|
$beforeLabel = Read-ButtonLabel $F_MODE $true
|
|
if (-not $beforeLabel) { throw "V-C5 precondition FAIL: mode label unreadable" }
|
|
[void](Tap-VisibleButton $F_MODE $true)
|
|
Start-Sleep -Milliseconds 600
|
|
$afterLabel = Read-ButtonLabel $F_MODE $true
|
|
Write-Host " mode label: [$beforeLabel] -> [$afterLabel]"
|
|
# null would trivially "change" from a real label - assert non-empty too (R-8)
|
|
if (-not $afterLabel -or $beforeLabel -eq $afterLabel) { throw "V-C5 FAIL: mode tap changed nothing" }
|
|
# converge back to relative (the product default the later proofs assume),
|
|
# regardless of which way the flip above landed - tap until it reads relative.
|
|
$F_MODE_REL = $F_MODE + -join ([char]0x76F8,[char]0x5BF9) # mode: relative
|
|
$converged = $false
|
|
for ($i=0; $i -lt 3 -and -not $converged; $i++) {
|
|
$cur = Read-ButtonLabel $F_MODE $true
|
|
if ($cur -eq $F_MODE_REL) { $converged = $true; break }
|
|
[void](Tap-VisibleButton $F_MODE $true)
|
|
Start-Sleep -Milliseconds 600
|
|
}
|
|
$restored = Read-ButtonLabel $F_MODE $true
|
|
Write-Host " mode converged to relative: [$restored]"
|
|
if (-not $converged -or $restored -ne $F_MODE_REL) {
|
|
throw "V-C5 FAIL: mode not converged to relative (pollutes later proofs)" }
|
|
Write-Host " PASS: buttons work, state restored"
|
|
|
|
# =====================================================================
|
|
Write-Host "=== 7. V-C7: orientation cycle AUTO->L->P->SYS->AUTO ==="
|
|
$seq = @($F_LAND, $F_PORT, $F_SYS, $F_AUTO)
|
|
$wantLand = @($true, $false, $null, $true) # null = don't assert SYSTEM policy
|
|
for ($i=0; $i -lt 4; $i++) {
|
|
$orient = Find-VisibleButton $F_ORIENT $true
|
|
if (-not $orient) { throw "V-C7 FAIL: orientation button unreachable after 5 wakes" }
|
|
[void](Tap-VisibleButton $F_ORIENT $true)
|
|
Start-Sleep -Milliseconds 1400
|
|
$label = Read-ButtonLabel $F_ORIENT $true
|
|
$d = LocalDisplay
|
|
$land = $d.w -gt $d.h
|
|
Write-Host (" step {0}: label=[{1}] want=[{2}] display={3}x{4} landscape={5}" -f ($i+1),$label,$seq[$i],$d.w,$d.h,$land)
|
|
if ($label -ne $seq[$i]) { throw ("V-C7 FAIL: step {0} label [{1}] != [{2}]" -f ($i+1),$label,$seq[$i]) }
|
|
if ($null -ne $wantLand[$i] -and $land -ne $wantLand[$i]) {
|
|
throw ("V-C7 FAIL: step {0} landscape={1} want={2}" -f ($i+1),$land,$wantLand[$i]) }
|
|
}
|
|
Write-Host " PASS: full cycle with display following each policy"
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-c7-final.png" | Out-Null
|
|
}
|
|
finally {
|
|
# 4a pollution discipline: leave no live session behind - the next proof's
|
|
# Restart-AppForm force-stops the app mid-session, which strands the agent's
|
|
# serial accept in a zombie (N11) and the run after that fails to connect
|
|
# (measured 2026-11-01: agent alive but no longer serving handshakes).
|
|
[void](AdbShell @("am","force-stop","com.verify.remote"))
|
|
Start-Sleep -Milliseconds 500
|
|
Get-Job | Stop-Job -ErrorAction SilentlyContinue
|
|
Get-Job | Remove-Job -Force -ErrorAction SilentlyContinue
|
|
[void](AdbShell @("svc","power","stayon","false"))
|
|
if ($origIme -and $origIme -ne "null") {
|
|
[void](AdbShell @("ime","enable",$origIme))
|
|
[void](AdbShell @("ime","set",$origIme))
|
|
}
|
|
}
|
|
|
|
Write-Host "=== LANDSCAPE PROOF COMPLETE ==="
|