fix(android): BACK 键改用自测键盘可见性——isAcceptingText 会让会话永远断不掉
真机实测(vivo S6 + 讯飞,2026-10-30):软键盘已隐藏、mInputShown=false, 但 imm.isAcceptingText 仍返回 true——只要 InputConnection 还绑着它就是 true。 原先用它判断「先关键盘还是先断会话」,后果是第一次 BACK 空转,第二次也进不来 (我们已 return true),会话再也断不掉。这不是推测,是完整链路 proof 跑到 step 5 时实测失败的。 改为自测:比较根窗口可见区域与窗口高度,被 IME 吃掉超过 15% 视为可见; 用 OnGlobalLayoutListener 被动跟踪(没有公开 API 能直接查询,API 34 上 getInputMethodWindowVisibleHeight 也不是公开接口)。阈值用比例不用固定像素。 验证:diag 依次显示「键盘可见」→「键盘隐藏」→ 会话断开回表单; 随后完整链路 proof 与中文 proof 均复跑通过。 同时修正 phone_chain_proof 的 V4 断言:uiautomator 属性顺序是 text= 在 class= 之前,原正则要求反序,导致打印空表单行仍判 PASS(假绿)。 空 EditText 的 text 会回显 hint,所以判据改为比对实际输入值。
This commit is contained in:
parent
9792662181
commit
8993a38658
@ -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) {
|
||||
|
||||
@ -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, '<node[^>]*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, '<node[^>]*>')) {
|
||||
$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, '<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