remote/tools/emoji_manual_proof.ps1
郑博文 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

194 lines
8.1 KiB
PowerShell

# 2b V7 (device side): manual emoji acceptance helper.
# ASCII-only source (PS 5.1 + BOM-less = GBK corruption; see personal lessons).
#
# WHY MANUAL: the IME's own emoji panel is invisible to uiautomator (dump shows
# only our app's nodes), so adb cannot drive emoji selection. The human taps the
# emoji; this script only prepares the receiving window and reads back what
# actually arrived - the judgement is still made against real received bytes.
#
# What this proves: the phone IME commits a supplementary-plane character
# (surrogate pair) as ONE commitText, and protocol v3 delivers both code units
# adjacently so Windows synthesises a single character.
# The Windows half is already proven mechanically (V7' in text_injection_proof).
#
# Usage:
# powershell -ExecutionPolicy Bypass -File tools\emoji_manual_proof.ps1
# ... then, when prompted, tap an emoji on the phone's keyboard.
param(
# 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
)
$ErrorActionPreference = "Stop"
$repo = (Resolve-Path "$PSScriptRoot\..").Path
$adb = "D:\working\android\sdk\platform-tools\adb.exe"
$tmp = "$env:TEMP\remote-emoji-proof"
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$pidFile = "$tmp\target.pid"
$stopFile = "$tmp\stop.flag"
$resultFile = "$tmp\received.txt"
Remove-Item $pidFile,$stopFile,$resultFile -ErrorAction SilentlyContinue
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class FgEmoji {
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
"@
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 Wait-File {
param([string]$Path, [int]$TimeoutMs = 20000)
$t = 0
while (-not (Test-Path $Path) -and $t -lt ($TimeoutMs / 150)) { Start-Sleep -Milliseconds 150; $t++ }
if (-not (Test-Path $Path)) { throw "timeout waiting for $Path" }
}
function Read-Received {
if (-not (Test-Path $resultFile)) { return "" }
$b = [System.IO.File]::ReadAllBytes($resultFile)
$s = [System.Text.Encoding]::UTF8.GetString($b)
if ($s.Length -gt 0 -and $s[0] -eq [char]0xFEFF) { $s = $s.Substring(1) }
return $s
}
function Show-Cps([string]$s) {
($s.ToCharArray() | ForEach-Object { "U+{0:X4}" -f [int]$_ }) -join " "
}
Write-Host "=== 1. retire earlier targets, ensure fresh agent ==="
$flags = @(Get-ChildItem -Path $env:TEMP -Recurse -Filter "stop.flag" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*remote*proof*" })
foreach ($f in $flags) { (Get-Item $f.FullName).LastWriteTime = Get-Date }
if ($flags.Count -gt 0) { Start-Sleep -Seconds 3 }
$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",$Port,"--code",$Code -WindowStyle Hidden
Start-Sleep -Seconds 1
Write-Host " agent started"
}
Write-Host "=== 2. target window (receives the injected text) ==="
Start-Process powershell.exe -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass",`
"-File","$repo\tools\text_target.ps1","-PidFile",$pidFile,`
"-ResultFile",$resultFile,"-StopFile",$stopFile,"-GrabForeground"
Wait-File $pidFile
$parts = @((Get-Content $pidFile -Raw) -split "`n")
$tid = [int]$parts[0].Trim()
$hwnd = [IntPtr][long]$parts[1].Trim()
Start-Sleep -Milliseconds 800
$fg = [FgEmoji]::GetForegroundWindow()
Write-Host " target pid=$tid hwnd=$($hwnd.ToInt64()) foreground=$($fg -eq $hwnd)"
Write-Host "=== 3. phone session + show keyboard ==="
[void](Adb-Shell @("am","force-stop","com.verify.remote"))
[void](Adb-Shell @("am","start","-n","com.verify.remote/.MainActivity",
"--es","host",$PhoneIp,"--ei","port","$Port","--es","code",$Code))
$deadline = (Get-Date).AddSeconds(25)
$ui = ""
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 900
[void](Adb-Shell @("uiautomator","dump","/sdcard/ui.xml"))
$old = $ErrorActionPreference; $ErrorActionPreference = "Continue"
try { & $adb pull /sdcard/ui.xml "$tmp\ui.xml" 2>$null | Out-Null } finally { $ErrorActionPreference = $old }
if (Test-Path "$tmp\ui.xml") { $ui = [System.IO.File]::ReadAllText("$tmp\ui.xml", [System.Text.Encoding]::UTF8) }
if ($ui -match "RTT=") { break }
}
if ($ui -notmatch "RTT=") { throw "phone did not connect" }
$btns = [regex]::Matches($ui, '<node[^>]*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
if ($btns.Count -lt 1) { throw "keyboard button not found" }
$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 "IME not bound to our InputConnection" }
Write-Host " IME shown and bound: $([regex]::Match($ime,'mCurId=(\S+)').Groups[1].Value)"
$baseline = Read-Received
Write-Host ""
Write-Host " ------------------------------------------------------------"
Write-Host " NOW ON THE PHONE: tap the emoji key, then tap ONE emoji."
Write-Host " (Keyboard is up; the Windows target window is receiving.)"
Write-Host " Waiting up to $WaitSeconds s for a supplementary-plane character..."
Write-Host " ------------------------------------------------------------"
Write-Host ""
$deadline = (Get-Date).AddSeconds($WaitSeconds)
$got = ""
$found = $false
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 500
$got = Read-Received
if ($got.Length -gt $baseline.Length) {
$new = $got.Substring($baseline.Length)
# any code unit in the surrogate range means an emoji (or other
# supplementary-plane char) reached us
foreach ($ch in $new.ToCharArray()) {
$cp = [int]$ch
if ($cp -ge 0xD800 -and $cp -le 0xDFFF) { $found = $true; break }
}
if ($found) { break }
}
}
$final = Read-Received
$newFinal = if ($final.Length -ge $baseline.Length) { $final.Substring($baseline.Length) } else { "" }
Write-Host "=== 4. result ==="
Write-Host " baseline : [$(Show-Cps $baseline)]"
Write-Host " final : [$(Show-Cps $final)]"
Write-Host " new chars: [$(Show-Cps $newFinal)]"
Write-Host " new units: $($newFinal.Length)"
if ($found) {
# verify the pair is adjacent and forms a real code point
$units = $newFinal.ToCharArray() | ForEach-Object { [int]$_ }
$paired = $false
for ($i = 0; $i -lt $units.Count - 1; $i++) {
if ($units[$i] -ge 0xD800 -and $units[$i] -le 0xDBFF -and
$units[$i+1] -ge 0xDC00 -and $units[$i+1] -le 0xDFFF) {
$cp = 0x10000 + (($units[$i] - 0xD800) * 0x400) + ($units[$i+1] - 0xDC00)
Write-Host (" V7 PASS: adjacent surrogate pair -> U+{0:X4} ({1} code units committed as one)" -f $cp, $newFinal.Length)
$paired = $true
break
}
}
if (-not $paired) { throw "V7 FAIL: surrogate units present but not an adjacent valid pair" }
if ($newFinal.Contains([char]0xFFFD)) { throw "V7 FAIL: U+FFFD present (pair was split)" }
}
else {
Write-Host " V7 INCONCLUSIVE: no supplementary-plane character arrived within $WaitSeconds s"
Write-Host " (If you tapped an emoji and nothing arrived, that IS the finding - record it.)"
}
Write-Host "=== 5. cleanup ==="
[void](Adb-Shell @("input","keyevent","4"))
New-Item -ItemType File -Force -Path $stopFile | Out-Null
Start-Sleep -Milliseconds 500
Write-Host "=== done ==="