From 97926621817c087c3227f5bcc733c02ef5e3c395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=8D=9A=E6=96=87?= <1323515022@qq.com> Date: Fri, 30 Oct 2026 13:15:27 +0800 Subject: [PATCH] =?UTF-8?q?test(android-2b):=20=E7=9C=9F=E6=9C=BA=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E5=80=99=E9=80=89=E8=AF=8D=20proof=E2=80=94=E2=80=94?= =?UTF-8?q?=E6=8B=BC=E9=9F=B3=E7=BB=84=E8=AF=8D=E7=BB=8F=20commitText=20?= =?UTF-8?q?=E8=90=BD=E5=88=B0=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V6 的判据是「手机中文输入法能把汉字送进远端」,此前只能靠人手点候选词。 实测发现两条关键事实,决定了 proof 的可行形态: 1. adb keyevent 相当于物理键盘输入,中文 IME **不组词**,字母原样透传到 Activity.onKeyDown——所以驱动候选词必须用 adb input text(走 IME)。 2. IME 自己的按键视图对 uiautomator 不可见(dump 里只有我们 App 的 15 个节点), 中/英切换按钮读不到坐标,只能按截图标定一次点击位;且该按钮**有状态**, 于是写成幂等重试:先试,只收到字母才翻转模式再来一次。 跑通结果:讯飞拼音 nihao + 空格 -> commitText 2u -> 靶窗收到 U+4F60 U+597D。 这补上了方案风险 R1(vivo ROM + 第三方中文 IME 行为未观测)的核心一环。 --- tools/ime_chinese_proof.ps1 | 205 ++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 tools/ime_chinese_proof.ps1 diff --git a/tools/ime_chinese_proof.ps1 b/tools/ime_chinese_proof.ps1 new file mode 100644 index 0000000..123b11a --- /dev/null +++ b/tools/ime_chinese_proof.ps1 @@ -0,0 +1,205 @@ +# 2b V6 on-device CHINESE: drive the IME through pinyin + candidate selection. +# ASCII-only source. Non-ASCII expectations are built from code points. +# +# Measured path differences (2026-10-30, vivo S6 + iFlytek IME): +# - `adb input keyevent ` behaves like a HARDWARE keyboard: the IME +# does not compose, the event lands in Activity.onKeyDown and our printable +# unicodeChar branch sends it as text. So keyevents CANNOT exercise Chinese +# candidate selection. +# - `adb input text ` is delivered to the IME, which composes it; +# SPACE (keyevent 62) then accepts the highlighted candidate, and the Hanzi +# arrive at RemoteInputConnection.commitText. +# This script therefore types pinyin with `input text` and commits with SPACE. +# +# Acceptance: the Windows target window receives the exact Han characters the +# IME committed - Chinese input works end to end over protocol v3. +# Not covered: swipe/handwriting input, browsing the candidate list. + +param( + [string]$PhoneIp = "192.168.1.180", + [int]$Port = 47800, + [string]$Code = "t" +) +$ErrorActionPreference = "Stop" +$repo = (Resolve-Path "$PSScriptRoot\..").Path +$adb = "D:\working\android\sdk\platform-tools\adb.exe" +$tmp = "$env:TEMP\remote-cjk2" + +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; +using System.Text; +public static class Fg5 { + [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); + [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); + [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n); + public static string Desc() { + IntPtr h = GetForegroundWindow(); uint pid; GetWindowThreadProcessId(h, out pid); + var sb = new StringBuilder(256); GetWindowText(h, sb, 256); + return h.ToInt64() + " [" + sb.ToString() + "] pid=" + pid; + } +} +"@ + +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 Assert-Foreground([IntPtr]$hwnd, [string]$where) { + $deadline = (Get-Date).AddMilliseconds(2500) + while ((Get-Date) -lt $deadline) { + if ([Fg5]::GetForegroundWindow() -eq $hwnd) { return } + Start-Sleep -Milliseconds 50 + } + throw "$where : foreground is $([Fg5]::Desc()), want target $($hwnd.ToInt64()) - refusing to inject" +} + +function Get-Ui { + [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 (-not (Test-Path "$tmp\ui.xml")) { return "" } + return [System.IO.File]::ReadAllText("$tmp\ui.xml", [System.Text.Encoding]::UTF8) +} + +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 "=== 0. retire ALL earlier targets, verify none holds foreground ===" +$flags = @(Get-ChildItem -Path $env:TEMP -Recurse -Filter "stop.flag" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*remote*cjk*" -or $_.FullName -like "*remote*proof*" }) +foreach ($f in $flags) { (Get-Item $f.FullName).LastWriteTime = Get-Date } +if ($flags.Count -gt 0) { Start-Sleep -Seconds 3 } +$hosts = @(Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | + Where-Object { $_.CommandLine -match 'text_target\.ps1' -and $_.ProcessId -ne $PID }) +foreach ($hx in $hosts) { Stop-Process -Id $hx.ProcessId -Force -ErrorAction SilentlyContinue } +if ($hosts.Count -gt 0) { Start-Sleep -Seconds 1 } +Write-Host " retired $($flags.Count) flag(s), killed $($hosts.Count) host(s); fg now: $([Fg5]::Desc())" + +Write-Host "=== 1. agent freshness ===" +$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 "=== 2. fresh target window (empty field) ===" +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() +Assert-Foreground $hwnd "after launch" +$base0 = Read-Received +Write-Host " target pid=$tid hwnd=$($hwnd.ToInt64()) baseline=[$(Show-Cps $base0)]" + +Write-Host "=== 3. phone session ===" +[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; $ui = Get-Ui; if ($ui -match "RTT=") { break } } +if ($ui -notmatch "RTT=") { throw "phone did not connect" } + +Write-Host "=== 4. show IME (tap keyboard button) ===" +$btns = [regex]::Matches($ui, ']*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: $([regex]::Match($ime,'mCurId=(\S+)').Groups[1].Value)" + +Write-Host "=== 4b/5. pinyin -> IME candidate -> commitText (mode-tolerant) ===" +# Both IMEs on this device may remember an ENGLISH mode, in which case letters +# pass through without composing - which looks identical to "our pipeline lost +# the Hanzi" unless you check. The IME's key views are NOT visible to +# uiautomator (its dump contains only our app's nodes), so the ab/zh toggle is +# reached by a calibrated tap: iFlytek's key sits at display (73,1048) in a +# 1080x2400 capture => device (146,2096) at 2x. Verified 2026-10-30. +# The toggle is STATEFUL, so this is written as an idempotent attempt loop: +# try, and only if we see letters-without-Hanzi do we flip the mode and retry. +# A stray tap here changes a keyboard setting, nothing destructive. +$modeToggle = @(146, 2096) +$expect = -join @([char]0x4F60, [char]0x597D) # NI HAO +$attempt = 0 +$new = "" +while ($attempt -lt 2) { + $attempt++ + $base = Read-Received + Assert-Foreground $hwnd "attempt ${attempt}: before pinyin" + [void](Adb-Shell @("input","text","nihao")) + Start-Sleep -Milliseconds 1500 + $mid = Read-Received + Write-Host " attempt ${attempt} after pinyin (composing): [$(Show-Cps $mid)]" + Assert-Foreground $hwnd "attempt ${attempt}: before SPACE" + [void](Adb-Shell @("input","keyevent","62")) + Start-Sleep -Seconds 2 + $got = Read-Received + $new = if ($got.Length -ge $base.Length) { $got.Substring($base.Length) } else { "" } + Write-Host " attempt ${attempt} newly received: [$(Show-Cps $new)]" + if ($new -match [regex]::Escape($expect)) { break } + if ($attempt -eq 1) { + Write-Host " no Hanzi yet -> flipping IME language mode and retrying once" + [void](Adb-Shell @("input","tap","$($modeToggle[0])","$($modeToggle[1])")) + Start-Sleep -Seconds 2 + } +} +$diag = [regex]::Match((Get-Ui), 'text="([^"]*RTT=[^"]*)"').Groups[1].Value +Write-Host " phone status: $diag" +Write-Host " expected to contain: $(Show-Cps $expect)" +if ($new -notmatch [regex]::Escape($expect)) { + Write-Host " GROUND TRUTH: [$(Show-Cps $new)]" + Write-Host " U+006E.. letters only => IME stayed in English mode (re-calibrate the toggle tap)" + throw "V6 CHINESE FAIL: IME did not deliver Hanzi through commitText" +} +Write-Host " V6 PASS: pinyin -> IME candidate -> commitText -> v3 Text -> Windows Hanzi" + +Write-Host "=== 6. cleanup ===" +[void](Adb-Shell @("input","keyevent","4")) +New-Item -ItemType File -Force -Path $stopFile | Out-Null +Start-Sleep -Milliseconds 500 +Write-Host "=== CHINESE IME PROOF COMPLETE ==="