diff --git a/apps/android-viewer/app/src/main/kotlin/com/verify/remote/MainActivity.kt b/apps/android-viewer/app/src/main/kotlin/com/verify/remote/MainActivity.kt index 3766174..be060ca 100644 --- a/apps/android-viewer/app/src/main/kotlin/com/verify/remote/MainActivity.kt +++ b/apps/android-viewer/app/src/main/kotlin/com/verify/remote/MainActivity.kt @@ -94,6 +94,10 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener { root.addView(remotePanel, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)) setContentView(root) + // 键盘可见性只能靠窗口布局变化被动感知(没有公开 API 能直接查询)。 + // 每次根视图布局完成都重算一次,覆盖「用户点键盘按钮」「IME 自己收起」两条路径。 + root.viewTreeObserver.addOnGlobalLayoutListener { updateKeyboardVisibility() } + showConnectState("未连接") // intent 入口优先级高于表单默认值:build-android.ps1 与调试脚本依赖它 @@ -275,9 +279,44 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener { override fun isReadyForInput(): Boolean = handle != 0L + /** + * 软键盘是否可见(自测,不依赖 InputMethodManager 的间接信号)。 + * + * 为什么必须自测:真机实测(vivo S6 + 讯飞,2026-10-30)键盘隐藏后 + * `imm.isAcceptingText` **仍返回 true**——只要 InputConnection 还绑着它就一直 true。 + * 用它判断"先关键盘还是先断会话",会让第一次 BACK 空转、第二次也进不来 + * (我们已 return true),会话再也断不掉。这是真机暴露的缺陷,不是推测。 + * + * 做法:比较根窗口可见区域高度与窗口高度。键盘占位时可见区域显著变矮。 + * 阈值用 15% 而不是固定像素,避免不同屏幕密度下误判。 + */ + private var keyboardVisible = false + + private val visibleFrame = android.graphics.Rect() + + private fun updateKeyboardVisibility() { + val decor = window.decorView + decor.getWindowVisibleDisplayFrame(visibleFrame) + val screenH = decor.height.takeIf { it > 0 } ?: return + val visibleH = visibleFrame.height() + // 底部被 IME 吃掉的高度超过屏幕 15% → 认为键盘可见 + val hiddenByIme = screenH - visibleH + val nowVisible = hiddenByIme > screenH * 0.15 + if (nowVisible != keyboardVisible) { + keyboardVisible = nowVisible + lastTouchDbg = if (nowVisible) "键盘可见" else "键盘隐藏" + refreshStatus() + } + } + + override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { + super.onConfigurationChanged(newConfig) + updateKeyboardVisibility() + } + /** * 按键转发(物理键盘 / adb 注入 / 无 IME 时的软键盘): - * - 返回键:先收 IME,否则断开回表单(阶段 2a 语义延续); + * - 返回键:**键盘可见时**先收键盘,否则断开回表单(阶段 2a 语义延续); * - **可打印**的 `unicodeChar`(0x20 起、排除 0x7F)→ v3 **Text** 通道: * 字符注入与远端键盘布局无关。注意 `getUnicodeChar()` 给的是单个 * UTF-16 code unit 而非码点,所以这条路只覆盖 BMP;增补平面字符由 @@ -295,10 +334,13 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener { */ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { - val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - // isAcceptingText = 当前有 IME 连接且正在接收输入(API 17+) - if (imm.isAcceptingText) { + // 用自测的键盘可见性,不用 imm.isAcceptingText(见 updateKeyboardVisibility + // 注释:真机上键盘隐藏后它仍为 true,会让会话永远断不掉) + updateKeyboardVisibility() + if (keyboardVisible) { + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(surfaceView.windowToken, 0) + keyboardVisible = false return true } if (handle != 0L) { diff --git a/tools/phone_chain_proof.ps1 b/tools/phone_chain_proof.ps1 index db3dfea..4fd3914 100644 --- a/tools/phone_chain_proof.ps1 +++ b/tools/phone_chain_proof.ps1 @@ -230,9 +230,21 @@ try { for ($i = 0; $i -lt 6; $i++) { [void](Adb-Shell @("input", "keyevent", "67")) } [void](Adb-Shell @("input", "keyevent", "48")) $xml3 = Get-PhoneUiText - $vals = [regex]::Matches($xml3, ']*class="android\.widget\.EditText"[^>]*text="([^"]*)"') | - ForEach-Object { $_.Groups[1].Value } + # Attribute order in uiautomator dumps is text= ... class= ... bounds=; + # matching "class=...text=" silently finds nothing (seen 2026-10-30: printed + # an empty form line). Parse per-node and pull attributes independently. + $vals = @() + foreach ($n in [regex]::Matches($xml3, ']*>')) { + $v = $n.Value + if ($v -match 'class="android\.widget\.EditText"') { + $vals += [regex]::Match($v, 'text="([^"]*)"').Groups[1].Value + } + } + # NOTE: an EMPTY EditText reports its hint as text (verified: text="被控端 IP"), + # 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" } $btn = [regex]::Match($xml3, ']*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') if (-not $btn.Success) { throw "connect button not found" } $bc = Center $btn