From 1513baca0ffd397026db6a6b239671c493bd9a61 Mon Sep 17 00:00:00 2001 From: "DeanT.Maxim" <22721535+DeanTMaxim@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:07:45 +0800 Subject: [PATCH] fix: validate missing accessibility event windows Prevent delayed window-state events from replacing the foreground package cache unless the active root confirms both the event window and package. Add policy tests for both maintained namespaces. --- .../core/activity/ActivityInfoProvider.kt | 24 ++++- .../core/activity/ActivityInfoProvider.kt | 24 ++++- .../ActivityInfoProviderPolicyTest.kt | 87 +++++++++++++++++++ .../ActivityInfoProviderPolicyTest.kt | 87 +++++++++++++++++++ 4 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/com/stardust/autojs/core/activity/ActivityInfoProviderPolicyTest.kt create mode 100644 app/src/test/java/org/autojs/autojs/core/activity/ActivityInfoProviderPolicyTest.kt diff --git a/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt b/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt index 404684ecb..11b251a2c 100644 --- a/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt +++ b/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt @@ -78,9 +78,15 @@ class ActivityInfoProvider(private val context: Context) : AccessibilityDelegate override fun onAccessibilityEvent(service: AccessibilityService, event: AccessibilityEvent): Boolean { if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { val window = service.getWindow(event.windowId) - if (window?.isFocused != false) { + val activeRoot = if (window == null) service.rootInActiveWindow else null + if (shouldAcceptWindowStateChange( + eventWindowId = event.windowId, + eventPackage = event.packageName, + eventWindowFocused = window?.isFocused, + activeRootWindowId = activeRoot?.windowId, + activeRootPackage = activeRoot?.packageName, + )) { setLatestComponent(event.packageName, event.className) - return false } } return false @@ -189,6 +195,20 @@ class ActivityInfoProvider(private val context: Context) : AccessibilityDelegate } } +internal fun shouldAcceptWindowStateChange( + eventWindowId: Int, + eventPackage: CharSequence?, + eventWindowFocused: Boolean?, + activeRootWindowId: Int?, + activeRootPackage: CharSequence?, +): Boolean = when (eventWindowFocused) { + true -> true + false -> false + null -> eventPackage != null && + eventWindowId == activeRootWindowId && + eventPackage.toString() == activeRootPackage?.toString() +} + private fun AccessibilityService.getWindow(windowId: Int): AccessibilityWindowInfo? { windows.forEach { if (it.id == windowId) { diff --git a/app/src/main/java/org/autojs/autojs/core/activity/ActivityInfoProvider.kt b/app/src/main/java/org/autojs/autojs/core/activity/ActivityInfoProvider.kt index 51ae1947d..00d7110e5 100644 --- a/app/src/main/java/org/autojs/autojs/core/activity/ActivityInfoProvider.kt +++ b/app/src/main/java/org/autojs/autojs/core/activity/ActivityInfoProvider.kt @@ -77,9 +77,15 @@ class ActivityInfoProvider(private val context: Context) : AccessibilityDelegate override fun onAccessibilityEvent(service: AccessibilityService, event: AccessibilityEvent): Boolean { if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { val window = service.getWindow(event.windowId) - if (window?.isFocused != false) { + val activeRoot = if (window == null) service.rootInActiveWindow else null + if (shouldAcceptWindowStateChange( + eventWindowId = event.windowId, + eventPackage = event.packageName, + eventWindowFocused = window?.isFocused, + activeRootWindowId = activeRoot?.windowId, + activeRootPackage = activeRoot?.packageName, + )) { setLatestComponent(event.packageName, event.className) - return false } } return false @@ -191,6 +197,20 @@ class ActivityInfoProvider(private val context: Context) : AccessibilityDelegate } } +internal fun shouldAcceptWindowStateChange( + eventWindowId: Int, + eventPackage: CharSequence?, + eventWindowFocused: Boolean?, + activeRootWindowId: Int?, + activeRootPackage: CharSequence?, +): Boolean = when (eventWindowFocused) { + true -> true + false -> false + null -> eventPackage != null && + eventWindowId == activeRootWindowId && + eventPackage.toString() == activeRootPackage?.toString() +} + private fun AccessibilityService.getWindow(windowId: Int): AccessibilityWindowInfo? { windows.forEach { if (it.id == windowId) { diff --git a/app/src/test/java/com/stardust/autojs/core/activity/ActivityInfoProviderPolicyTest.kt b/app/src/test/java/com/stardust/autojs/core/activity/ActivityInfoProviderPolicyTest.kt new file mode 100644 index 000000000..a09ac51aa --- /dev/null +++ b/app/src/test/java/com/stardust/autojs/core/activity/ActivityInfoProviderPolicyTest.kt @@ -0,0 +1,87 @@ +package com.stardust.autojs.core.activity + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ActivityInfoProviderPolicyTest { + + @Test + fun `accepts a focused event window`() { + assertTrue( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = true, + activeRootWindowId = 8, + activeRootPackage = "com.example.other", + ), + ) + } + + @Test + fun `rejects an unfocused event window even when the active root matches`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = false, + activeRootWindowId = 7, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window when the active root window differs`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 8, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window when the active root package differs`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = "com.example.other", + ), + ) + } + + @Test + fun `accepts a missing event window verified by the active root`() { + assertTrue( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window with no package evidence`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = null, + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = null, + ), + ) + } + +} diff --git a/app/src/test/java/org/autojs/autojs/core/activity/ActivityInfoProviderPolicyTest.kt b/app/src/test/java/org/autojs/autojs/core/activity/ActivityInfoProviderPolicyTest.kt new file mode 100644 index 000000000..7b0c1199d --- /dev/null +++ b/app/src/test/java/org/autojs/autojs/core/activity/ActivityInfoProviderPolicyTest.kt @@ -0,0 +1,87 @@ +package org.autojs.autojs.core.activity + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ActivityInfoProviderPolicyTest { + + @Test + fun `accepts a focused event window`() { + assertTrue( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = true, + activeRootWindowId = 8, + activeRootPackage = "com.example.other", + ), + ) + } + + @Test + fun `rejects an unfocused event window even when the active root matches`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = false, + activeRootWindowId = 7, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window when the active root window differs`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 8, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window when the active root package differs`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = "com.example.other", + ), + ) + } + + @Test + fun `accepts a missing event window verified by the active root`() { + assertTrue( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = "com.example.app", + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = "com.example.app", + ), + ) + } + + @Test + fun `rejects a missing event window with no package evidence`() { + assertFalse( + shouldAcceptWindowStateChange( + eventWindowId = 7, + eventPackage = null, + eventWindowFocused = null, + activeRootWindowId = 7, + activeRootPackage = null, + ), + ) + } + +}