Commit 1c662a14bf3b509170b1cb4de3d05380b22e78e5
1 parent
2a044a3d
feat: A11y chat list 滚动查找(方案 A + C 兜底)
问题:目标会话被大量群消息挤出可见 chat list → FSM CHAT_LIST 状态 findChatListRow 找不到 → 直接退搜索 → 搜索 UI 时序脆弱经常卡死。 方案 A(主):CHAT_LIST 找不到目标时,dispatchGesture 上滑列表 (540,1500→540,600)逐屏 scan,会话一定在列表里只是被挤下去。 - chatListScrolls 计数,上限 MAX_CHATLIST_SCROLLS=6 - 每条消息在 onReceive 重置 - 滚动不计入 transitionCount(用自己的上限防死循环) 方案 C(兜底):滚到上限还找不到才 performSearch 退搜索。 新增 scrollChatListDown(onCompleted) — 合成上滑手势。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Showing
1 changed file
with
30 additions
and
1 deletions
wechat_plain/scripts/lsposed-wechat-sendmsg/app/src/main/java/com/hermes/wechat/xposed/accessibility/HermesAccessibilityService.java
| @@ -54,6 +54,8 @@ public class HermesAccessibilityService extends AccessibilityService { | @@ -54,6 +54,8 @@ public class HermesAccessibilityService extends AccessibilityService { | ||
| 54 | private String pendingContent = null; | 54 | private String pendingContent = null; |
| 55 | private String pendingQuoteText = null; // 要引用的消息片段(substring 匹配 bkl text),null=不引用 | 55 | private String pendingQuoteText = null; // 要引用的消息片段(substring 匹配 bkl text),null=不引用 |
| 56 | private String pendingAtSender = null; // 要 @ 的群成员显示名,null=不 @ | 56 | private String pendingAtSender = null; // 要 @ 的群成员显示名,null=不 @ |
| 57 | + private int chatListScrolls = 0; // chat list 滚动查找次数(每条消息重置) | ||
| 58 | + private static final int MAX_CHATLIST_SCROLLS = 6; // 滚动上限,超了才退搜索 | ||
| 57 | 59 | ||
| 58 | @Override | 60 | @Override |
| 59 | protected void onServiceConnected() { | 61 | protected void onServiceConnected() { |
| @@ -108,6 +110,7 @@ public class HermesAccessibilityService extends AccessibilityService { | @@ -108,6 +110,7 @@ public class HermesAccessibilityService extends AccessibilityService { | ||
| 108 | pendingContent = content; | 110 | pendingContent = content; |
| 109 | pendingQuoteText = (quoteText != null && !quoteText.isEmpty()) ? quoteText : null; | 111 | pendingQuoteText = (quoteText != null && !quoteText.isEmpty()) ? quoteText : null; |
| 110 | pendingAtSender = (atSender != null && !atSender.isEmpty()) ? atSender : null; | 112 | pendingAtSender = (atSender != null && !atSender.isEmpty()) ? atSender : null; |
| 113 | + chatListScrolls = 0; | ||
| 111 | 114 | ||
| 112 | // 拉起微信主界面(如果不在则会启动;如果在则保留当前 fragment 状态) | 115 | // 拉起微信主界面(如果不在则会启动;如果在则保留当前 fragment 状态) |
| 113 | Intent launch = getPackageManager().getLaunchIntentForPackage("com.tencent.mm"); | 116 | Intent launch = getPackageManager().getLaunchIntentForPackage("com.tencent.mm"); |
| @@ -210,9 +213,19 @@ public class HermesAccessibilityService extends AccessibilityService { | @@ -210,9 +213,19 @@ public class HermesAccessibilityService extends AccessibilityService { | ||
| 210 | row.recycle(); | 213 | row.recycle(); |
| 211 | root.recycle(); | 214 | root.recycle(); |
| 212 | mainHandler.postDelayed(() -> driveStateMachine(transitionCount + 1), 2200); | 215 | mainHandler.postDelayed(() -> driveStateMachine(transitionCount + 1), 2200); |
| 216 | + } else if (chatListScrolls < MAX_CHATLIST_SCROLLS) { | ||
| 217 | + // A 方案:目标不在可见列表 → 滚动列表逐屏找(会话一定在列表里,只是被挤下去) | ||
| 218 | + chatListScrolls++; | ||
| 219 | + Log.i(TAG, "FSM: " + pendingToUserDisplayName + " not visible, scrolling chat list (" | ||
| 220 | + + chatListScrolls + "/" + MAX_CHATLIST_SCROLLS + ")"); | ||
| 221 | + root.recycle(); | ||
| 222 | + // 滚动不计入 transitionCount(用 chatListScrolls 自己的上限防死循环) | ||
| 223 | + scrollChatListDown(() -> mainHandler.postDelayed( | ||
| 224 | + () -> driveStateMachine(transitionCount), 700)); | ||
| 213 | } else { | 225 | } else { |
| 226 | + // C 方案:滚到底还找不到 → 退回搜索 | ||
| 214 | Log.w(TAG, "FSM: " + pendingToUserDisplayName | 227 | Log.w(TAG, "FSM: " + pendingToUserDisplayName |
| 215 | - + " not in visible chat list, falling back to search"); | 228 | + + " not found after " + chatListScrolls + " scrolls, falling back to search"); |
| 216 | performSearch(root); | 229 | performSearch(root); |
| 217 | // performSearch owns root from here | 230 | // performSearch owns root from here |
| 218 | } | 231 | } |
| @@ -502,6 +515,22 @@ public class HermesAccessibilityService extends AccessibilityService { | @@ -502,6 +515,22 @@ public class HermesAccessibilityService extends AccessibilityService { | ||
| 502 | } | 515 | } |
| 503 | } | 516 | } |
| 504 | 517 | ||
| 518 | + /** 上滑会话列表(露出下面更多会话),用合成手势。完成后回调。 */ | ||
| 519 | + private void scrollChatListDown(Runnable onCompleted) { | ||
| 520 | + Path path = new Path(); | ||
| 521 | + // 从屏幕中下部往上滑(露出列表下方的会话);坐标按竖屏估算,避开顶部搜索栏和底部 nav | ||
| 522 | + path.moveTo(540, 1500); | ||
| 523 | + path.lineTo(540, 600); | ||
| 524 | + GestureDescription gesture = new GestureDescription.Builder() | ||
| 525 | + .addStroke(new GestureDescription.StrokeDescription(path, 0, 300)) | ||
| 526 | + .build(); | ||
| 527 | + boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() { | ||
| 528 | + @Override public void onCompleted(GestureDescription g) { if (onCompleted != null) onCompleted.run(); } | ||
| 529 | + @Override public void onCancelled(GestureDescription g) { if (onCompleted != null) onCompleted.run(); } | ||
| 530 | + }, mainHandler); | ||
| 531 | + if (!dispatched && onCompleted != null) onCompleted.run(); | ||
| 532 | + } | ||
| 533 | + | ||
| 505 | /** 在长按菜单中点击「引用」 — 搜索所有可见窗口(菜单可能在 popup window 里) */ | 534 | /** 在长按菜单中点击「引用」 — 搜索所有可见窗口(菜单可能在 popup window 里) */ |
| 506 | private void clickQuoteMenuItem() { | 535 | private void clickQuoteMenuItem() { |
| 507 | AccessibilityNodeInfo menuItem = findClickableByTextAllWindows(MENU_QUOTE_TEXT); | 536 | AccessibilityNodeInfo menuItem = findClickableByTextAllWindows(MENU_QUOTE_TEXT); |