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:
1 parent
9792662181
commit
8993a38658
2 files changed
+60
-6
No files matched your search
@@ -94,6 +94,10 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
|
|||||||
root.addView(remotePanel, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f))
|
root.addView(remotePanel, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f))
|
||||||
setContentView(root)
|
setContentView(root)
|
||||||
|
|
||||||
|
// 键盘可见性只能靠窗口布局变化被动感知(没有公开 API 能直接查询)。
|
||||||
|
// 每次根视图布局完成都重算一次,覆盖「用户点键盘按钮」「IME 自己收起」两条路径。
|
||||||
|
root.viewTreeObserver.addOnGlobalLayoutListener { updateKeyboardVisibility() }
|
||||||
|
|
||||||
showConnectState("未连接")
|
showConnectState("未连接")
|
||||||
|
|
||||||
// intent 入口优先级高于表单默认值:build-android.ps1 与调试脚本依赖它
|
// intent 入口优先级高于表单默认值:build-android.ps1 与调试脚本依赖它
|
||||||
@@ -275,9 +279,44 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
|
|||||||
|
|
||||||
override fun isReadyForInput(): Boolean = handle != 0L
|
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 时的软键盘):
|
* 按键转发(物理键盘 / adb 注入 / 无 IME 时的软键盘):
|
||||||
* - 返回键:先收 IME,否则断开回表单(阶段 2a 语义延续);
|
* - 返回键:**键盘可见时**先收键盘,否则断开回表单(阶段 2a 语义延续);
|
||||||
* - **可打印**的 `unicodeChar`(0x20 起、排除 0x7F)→ v3 **Text** 通道:
|
* - **可打印**的 `unicodeChar`(0x20 起、排除 0x7F)→ v3 **Text** 通道:
|
||||||
* 字符注入与远端键盘布局无关。注意 `getUnicodeChar()` 给的是单个
|
* 字符注入与远端键盘布局无关。注意 `getUnicodeChar()` 给的是单个
|
||||||
* UTF-16 code unit 而非码点,所以这条路只覆盖 BMP;增补平面字符由
|
* UTF-16 code unit 而非码点,所以这条路只覆盖 BMP;增补平面字符由
|
||||||
@@ -295,10 +334,13 @@ class MainActivity : Activity(), RemoteSurfaceView.Listener {
|
|||||||
*/
|
*/
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
// 用自测的键盘可见性,不用 imm.isAcceptingText(见 updateKeyboardVisibility
|
||||||
// isAcceptingText = 当前有 IME 连接且正在接收输入(API 17+)
|
// 注释:真机上键盘隐藏后它仍为 true,会让会话永远断不掉)
|
||||||
if (imm.isAcceptingText) {
|
updateKeyboardVisibility()
|
||||||
|
if (keyboardVisible) {
|
||||||
|
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(surfaceView.windowToken, 0)
|
imm.hideSoftInputFromWindow(surfaceView.windowToken, 0)
|
||||||
|
keyboardVisible = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (handle != 0L) {
|
if (handle != 0L) {
|
||||||
|
|||||||
@@ -230,9 +230,21 @@ try {
|
|||||||
for ($i = 0; $i -lt 6; $i++) { [void](Adb-Shell @("input", "keyevent", "67")) }
|
for ($i = 0; $i -lt 6; $i++) { [void](Adb-Shell @("input", "keyevent", "67")) }
|
||||||
[void](Adb-Shell @("input", "keyevent", "48"))
|
[void](Adb-Shell @("input", "keyevent", "48"))
|
||||||
$xml3 = Get-PhoneUiText
|
$xml3 = Get-PhoneUiText
|
||||||
$vals = [regex]::Matches($xml3, '<node[^>]*class="android\.widget\.EditText"[^>]*text="([^"]*)"') |
|
# Attribute order in uiautomator dumps is text= ... class= ... bounds=;
|
||||||
ForEach-Object { $_.Groups[1].Value }
|
# 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 ' | ')"
|
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+)\]"')
|
$btn = [regex]::Match($xml3, '<node[^>]*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
|
||||||
if (-not $btn.Success) { throw "connect button not found" }
|
if (-not $btn.Success) { throw "connect button not found" }
|
||||||
$bc = Center $btn
|
$bc = Center $btn
|
||||||
|
|||||||
Reference in new issue
Block a user