# 2b V6/V7 on-device IME path: does a real IME's commitText reach the remote # desktop? ASCII-only source (see personal lessons on PS 5.1 + BOM-less .ps1). # # Difference from phone_chain_proof.ps1: that one drives Activity.onKeyDown with # adb keyevents (IME hidden). Here the IME is SHOWN, so keys are consumed by the # input method and reach us only through RemoteInputConnection.commitText / # sendKeyEvent. That is the path Chinese candidates take, and it is the risk R1 # item in the plan (vivo ROM + third-party IME behaviour was never observed). # # What this proves mechanically: IME is bound to our InputConnection and its # committed text lands in a Windows window we own, via protocol v3. # What it cannot prove: human candidate-list selection (needs a finger); see # the result record - V6/V7 full judgement is listed as manual acceptance. # # Usage: # powershell -ExecutionPolicy Bypass -File tools\ime_commit_proof.ps1 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", [string]$Text = "hi" ) $ErrorActionPreference = "Stop" $repo = (Resolve-Path "$PSScriptRoot\..").Path $adb = "D:\working\android\sdk\platform-tools\adb.exe" $tmp = "$env:TEMP\remote-ime-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 FgApi2 { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); } "@ function Adb { param([Parameter(Mandatory)][string[]]$AdbArgs) $old = $ErrorActionPreference $ErrorActionPreference = "Continue" try { $out = & $adb @AdbArgs 2>$null return @{ out = ($out -join "`n"); code = $LASTEXITCODE } } finally { $ErrorActionPreference = $old } } function Adb-Shell { param([Parameter(Mandatory)][string[]]$Cmd) return Adb -AdbArgs (@("shell") + $Cmd) } 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(2000) while ((Get-Date) -lt $deadline) { if ([FgApi2]::GetForegroundWindow() -eq $hwnd) { return } Start-Sleep -Milliseconds 30 } throw "$where : foreground=$([FgApi2]::GetForegroundWindow()) want=$($hwnd.ToInt64()) - refusing to inject" } function Get-PhoneUiText { [void](Adb-Shell @("uiautomator", "dump", "/sdcard/ui.xml")) $r = Adb -AdbArgs @("pull", "/sdcard/ui.xml", "$tmp\ui.xml") if ($r.code -ne 0 -or -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 } Write-Host "=== 0. retire earlier targets (touch only existing stop flags) ===" $flags = @(Get-ChildItem -Path $env:TEMP -Recurse -Filter "stop.flag" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -like "*remote*proof*" }) if ($flags.Count -gt 0) { Write-Host " retiring $($flags.Count)" foreach ($f in $flags) { (Get-Item $f.FullName).LastWriteTime = Get-Date } Start-Sleep -Seconds 3 } Write-Host "=== 1. fresh target window ===" 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" Write-Host " target pid=$tid hwnd=$($hwnd.ToInt64())" Write-Host "=== 2. agent freshness + phone session ===" $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 } [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) $xml = "" while ((Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 900; $xml = Get-PhoneUiText; if ($xml -match "RTT=") { break } } if ($xml -notmatch "RTT=") { throw "phone did not connect" } Write-Host " $([regex]::Match($xml,'text="([^"]*RTT=[^"]*)"').Groups[1].Value)" Write-Host "=== 3. show the soft keyboard (tap the app's keyboard button) ===" # Button coordinates come from the live hierarchy. Our remote-state bar adds # "keyboard" before "disconnect", so index 0 is the keyboard button - asserted # rather than assumed by checking the count and printing what we tapped. $btns = [regex]::Matches($xml, ']*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') if ($btns.Count -lt 1) { throw "no buttons found in remote state" } $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) Write-Host " tapping first button ($kx,$ky)" [void](Adb-Shell @("input", "tap", "$kx", "$ky")) Start-Sleep -Seconds 3 $ime = (Adb-Shell @("dumpsys", "input_method")).out $shown = ($ime -match "mInputShown=true") $bound = ($ime -match "mHaveConnection=true") $curId = [regex]::Match($ime, "mCurId=(\S+)").Groups[1].Value Write-Host " IME: shown=$shown boundToUs=$bound cur=$curId" if (-not $shown) { throw "soft keyboard did not appear" } if (-not $bound) { throw "no InputConnection bound - our onCheckIsTextEditor/onCreateInputConnection path failed" } Write-Host "=== 4. type through the IME, expect commitText -> v3 Text -> Windows ===" $before = Read-Received Assert-Foreground $hwnd "before IME text" [void](Adb-Shell @("input", "text", $Text)) Start-Sleep -Milliseconds 1500 # Latin input in most Chinese IMEs commits without a candidate pick; if the IME # instead left it composing, nudge with SPACE to force a commit. $got = Read-Received if ($got -notmatch [regex]::Escape($Text)) { Write-Host " not committed yet (got [$got]); sending SPACE to accept candidate" [void](Adb-Shell @("input", "keyevent", "62")) # KEYCODE_SPACE Start-Sleep -Milliseconds 1500 $got = Read-Received } Write-Host " target received: [$got] (expected to contain [$Text])" $diag = [regex]::Match((Get-PhoneUiText), 'text="([^"]*RTT=[^"]*)"').Groups[1].Value Write-Host " phone status: $diag" if ($got -notmatch [regex]::Escape($Text)) { throw "IME COMMIT FAIL: nothing from the IME reached the Windows target" } Write-Host " PASS: real IME -> RemoteInputConnection -> v3 Text -> Windows window" Write-Host "=== 5. back key must close keyboard first, not the session ===" [void](Adb-Shell @("input", "keyevent", "4")) Start-Sleep -Seconds 2 $ime2 = (Adb-Shell @("dumpsys", "input_method")).out $xml2 = Get-PhoneUiText $stillLive = ($xml2 -match "RTT=") $kbHidden = ($ime2 -match "mInputShown=false") Write-Host " after BACK: session live=$stillLive keyboard hidden=$kbHidden" if (-not ($kbHidden -and $stillLive)) { throw "BACK did not behave as keyboard-first-then-session" } Write-Host " PASS: first BACK dismissed the keyboard, session kept" Write-Host "=== 6. cleanup ===" New-Item -ItemType File -Force -Path $stopFile | Out-Null Start-Sleep -Milliseconds 500 Write-Host "=== IME COMMIT PROOF COMPLETE ==="