test(android-2b): 真机 emoji 人工验收工具 + 链路 proof 参数化主机地址
V7 设备侧此前转人工,原因是 IME 的 emoji 面板对 uiautomator 不可见、adb 无法 驱动。工具只负责准备接收窗口并读回**实际到达的码元**,点 emoji 由人完成—— 判据仍是真实字节,不是截图。 真机实测通过:靶窗收到 U+D83D U+DE02 -> U+1F602,两个 code unit 相邻成对、 无 U+FFFD。这证明手机 IME 把增补平面字符作为一次 commitText 送出,协议 v3 保住了代理对相邻性。至此 V7 双侧都有证据。 同时修掉 phone_chain_proof 里硬编码的 192.168.1.180:本机 LAN 地址变成 192.168.1.110 后,表单连接打向旧地址超时,看起来像产品缺陷,实为脚本过期。 改为从 -PhoneIp 参数逐字符生成 keyevent,并在断言里比对实际输入值。
This commit is contained in:
parent
8993a38658
commit
99ffffa393
191
tools/emoji_manual_proof.ps1
Normal file
191
tools/emoji_manual_proof.ps1
Normal file
@ -0,0 +1,191 @@
|
||||
# 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(
|
||||
[string]$PhoneIp = "192.168.1.180",
|
||||
[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 ==="
|
||||
@ -222,9 +222,18 @@ try {
|
||||
[void](Adb-Shell @("input", "tap", "$($ipC[0])", "$($ipC[1])"))
|
||||
Start-Sleep -Milliseconds 400
|
||||
for ($i = 0; $i -lt 24; $i++) { [void](Adb-Shell @("input", "keyevent", "67")) }
|
||||
foreach ($k in @(8,16,9,56,8,13,15,56,8,56,8,15,7)) { # 192.168.1.180
|
||||
[void](Adb-Shell @("input", "keyevent", "$k"))
|
||||
# Type the SAME host this run was given. A hardcoded literal broke this
|
||||
# proof on 2026-10-31 when the dev box's LAN address changed
|
||||
# (192.168.1.180 -> 192.168.1.110): the form dialed the stale address,
|
||||
# timed out, and it looked like a product failure when the harness was
|
||||
# simply out of date. Digits are KEYCODE_0..9 = 7..16, '.' = 56.
|
||||
$ipKeys = @()
|
||||
foreach ($ch in $PhoneIp.ToCharArray()) {
|
||||
if ($ch -eq '.') { $ipKeys += 56 }
|
||||
elseif ($ch -ge '0' -and $ch -le '9') { $ipKeys += (7 + [int][string]$ch) }
|
||||
else { throw "unsupported character in PhoneIp: $ch" }
|
||||
}
|
||||
foreach ($k in $ipKeys) { [void](Adb-Shell @("input", "keyevent", "$k")) }
|
||||
[void](Adb-Shell @("input", "tap", "$($codeC[0])", "$($codeC[1])"))
|
||||
Start-Sleep -Milliseconds 400
|
||||
for ($i = 0; $i -lt 6; $i++) { [void](Adb-Shell @("input", "keyevent", "67")) }
|
||||
@ -244,7 +253,7 @@ try {
|
||||
# so "field text == typed value" is the assertion, not "field text non-empty".
|
||||
Write-Host " form now: $($vals -join ' | ')"
|
||||
if ($vals.Count -lt 3) { throw "expected 3 fields after typing, got $($vals.Count)" }
|
||||
if ($vals[0] -ne "192.168.1.180") { throw "V4 FAIL: IP field is [$($vals[0])], typing did not land" }
|
||||
if ($vals[0] -ne $PhoneIp) { throw "V4 FAIL: IP field is [$($vals[0])], expected [$PhoneIp]" }
|
||||
$btn = [regex]::Match($xml3, '<node[^>]*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
|
||||
if (-not $btn.Success) { throw "connect button not found" }
|
||||
$bc = Center $btn
|
||||
|
||||
Loading…
Reference in New Issue
Block a user