6.7.0 - Alpha13 - 修复 auto.registerEvent 注册的无障碍服务事件会被其他脚本误清理的问题 (issue #466)

This commit is contained in:
SuperMonster003
2025-12-29 22:29:57 +08:00
parent 0b38cd9d44
commit 517872f620
5 changed files with 93 additions and 32 deletions

View File

@@ -9,11 +9,11 @@ import android.view.accessibility.AccessibilityNodeInfo
import org.autojs.autojs.core.accessibility.AccessibilityTool.Companion.DEFAULT_A11Y_SERVICE_START_TIMEOUT
import org.autojs.autojs.core.accessibility.SimpleActionAutomator.Companion.AccessibilityEventCallback
import org.autojs.autojs.core.automator.AccessibilityEventWrapper
import org.autojs.autojs.event.EventDispatcher
import org.autojs.autojs.core.pref.Language
import org.autojs.autojs.event.EventDispatcher
import org.autojs.autojs.ui.main.drawer.DrawerFragment.Companion.Event.AccessibilityServiceStateChangedEvent
import org.greenrobot.eventbus.EventBus
import java.util.TreeMap
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock
/**
* Created by Stardust on May 2, 2017.
* Modified by SuperMonster003 as of Mar 20, 2022.
* Modified by SuperMonster003 as of Dec 29, 2025.
*/
open class AccessibilityService : android.accessibilityservice.AccessibilityService() {
@@ -30,7 +30,9 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
var fastRootInActiveWindow: AccessibilityNodeInfo? = null
var bridge: AccessibilityBridge? = null
private val eventBox = TreeMap<Int, AccessibilityEventCallback?>()
// eventType -> (ownerId -> callback)
private val eventBox = HashMap<Int, MutableMap<String, AccessibilityEventCallback?>>()
private val eventBoxLock = Any()
private val gestureEventDispatcher = EventDispatcher<GestureListener>()
@@ -43,23 +45,62 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
AccessibilityEvent::class.java.getField(
"TYPE_${event.uppercase(Language.getPrefLanguage().locale)}"
).get(null) as Int
} catch (unused: NoSuchFieldException) {
} catch (_: NoSuchFieldException) {
throw IllegalArgumentException("Unknown event: $event")
}
}
fun addAccessibilityEventCallback(name: String, callback: AccessibilityEventCallback?) {
eventBox[eventNameToType(name)] = callback
fun addAccessibilityEventCallback(ownerId: String, name: String, callback: AccessibilityEventCallback?) {
val type = eventNameToType(name)
synchronized(eventBoxLock) {
val bucket = eventBox.getOrPut(type) { HashMap() }
if (callback == null) {
bucket.remove(ownerId)
if (bucket.isEmpty()) eventBox.remove(type)
} else {
bucket[ownerId] = callback
}
}
}
fun removeAccessibilityEventCallback(name: String) {
eventBox.remove(eventNameToType(name))
fun removeAccessibilityEventCallback(ownerId: String, name: String) {
val type = eventNameToType(name)
synchronized(eventBoxLock) {
val bucket = eventBox[type] ?: return
bucket.remove(ownerId)
if (bucket.isEmpty()) eventBox.remove(type)
}
}
fun removeAllAccessibilityEventCallbacks(ownerId: String) {
synchronized(eventBoxLock) {
val it = eventBox.entries.iterator()
while (it.hasNext()) {
val entry = it.next()
entry.value.remove(ownerId)
if (entry.value.isEmpty()) it.remove()
}
}
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
instance = this
val type = event.eventType
eventBox[type]?.onAccessibilityEvent(AccessibilityEventWrapper(event))
// Snapshot callbacks to avoid holding lock while invoking user code.
// zh-CN: 为回调建立快照, 以避免在调用用户代码时持锁.
val callbacks: List<AccessibilityEventCallback> = synchronized(eventBoxLock) {
val bucket = eventBox[type] ?: return@synchronized emptyList()
bucket.values.filterNotNull().toList()
}
if (callbacks.isNotEmpty()) {
val wrapper = AccessibilityEventWrapper(event)
callbacks.forEach { cb ->
cb.onAccessibilityEvent(wrapper)
}
}
if (containsAllEventTypes || eventTypes.contains(type)) {
if (type == TYPE_WINDOW_STATE_CHANGED || type == TYPE_VIEW_FOCUSED) {
rootInActiveWindow?.also { fastRootInActiveWindow = it }
@@ -67,11 +108,9 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
for ((_, delegate) in delegates) {
val types = delegate.eventTypes
if (types == null || types.contains(type)) {
// val start = System.currentTimeMillis()
if (delegate.onAccessibilityEvent(this@AccessibilityService, event)) {
break
}
// Log.v(TAG, "millis: " + (System.currentTimeMillis() - start) + " delegate: " + delegate::class.java.name)
}
}
}
@@ -101,11 +140,7 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
}
override fun getRootInActiveWindow(): AccessibilityNodeInfo? {
return try {
super.getRootInActiveWindow()
} catch (e: Exception) {
null
}
return runCatching { super.getRootInActiveWindow() }.getOrNull()
}
override fun onDestroy() {
@@ -177,13 +212,10 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
}
}
fun stop() = try {
fun stop() = runCatching {
instance?.disableSelf()
instance = null
true
} catch (e: Exception) {
false
}
}.isSuccess
fun waitForStarted(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT): Boolean {
if (hasInstance()) {
@@ -209,7 +241,11 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
@JvmStatic
fun clearAccessibilityEventCallback() {
instance?.eventBox?.clear()
instance?.let { svc ->
synchronized(svc.eventBoxLock) {
svc.eventBox.clear()
}
}
}
fun setCallback(listener: AccessibilityServiceCallback?) {

View File

@@ -34,9 +34,12 @@ import android.accessibilityservice.AccessibilityService as AndroidAccessibility
/**
* Created by Stardust on Apr 2, 2017.
* Modified by SuperMonster003 as of Dec 29, 2025.
*/
class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge, private val scriptRuntime: ScriptRuntime) {
private val mA11yEventOwnerId: String = scriptRuntime.ownerId
private val mGlobalActionAutomatorRaw by lazy {
GlobalActionAutomator(scriptRuntime.uiHandler.applicationContext, Handler(scriptRuntime.loopers.servantLooper)) {
ensureService()
@@ -198,12 +201,27 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
// ! zh-CN: 参考 Auto.js Pro.
fun registerEvent(eventName: String, callback: AccessibilityEventCallback?) {
ensureService()
AccessibilityService.instance?.addAccessibilityEventCallback(eventName, callback)
val service = AccessibilityService.instance ?: return
if (callback == null) {
service.removeAccessibilityEventCallback(mA11yEventOwnerId, eventName)
return
}
service.addAccessibilityEventCallback(mA11yEventOwnerId, eventName, callback)
}
// @Created by 抠脚本人 on Jul 10, 2023.
fun removeEvent(eventName: String) {
AccessibilityService.instance?.removeAccessibilityEventCallback(eventName)
AccessibilityService.instance?.removeAccessibilityEventCallback(mA11yEventOwnerId, eventName)
}
/**
* Called when the script exits, only clears accessibility
* event listeners registered by the current script.
* zh-CN: 脚本退出时调用, 只清理由当前脚本注册的无障碍事件监听.
*/
fun removeAllEventsForThisRuntime() {
AccessibilityService.instance?.removeAllAccessibilityEventCallbacks(mA11yEventOwnerId)
}
private fun performAction(simpleAction: SimpleAction): Boolean {

View File

@@ -11,7 +11,6 @@ import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.annotation.ScriptVariable
import org.autojs.autojs.concurrent.VolatileDispose
import org.autojs.autojs.core.accessibility.AccessibilityBridge
import org.autojs.autojs.core.accessibility.AccessibilityService
import org.autojs.autojs.core.accessibility.SimpleActionAutomator
import org.autojs.autojs.core.accessibility.monitor.CloseableManager
import org.autojs.autojs.core.activity.ActivityInfoProvider
@@ -177,8 +176,8 @@ import org.autojs.autojs.runtime.api.augment.util.VersionCodes as UtilVersionCod
/**
* Created by Stardust on Jan 27, 2017.
* Modified by SuperMonster003 as of Dec 1, 2021.
* Created by SuperMonster003 on May 24, 2024.
* Modified by SuperMonster003 as of Dec 29, 2025.
* Transformed by SuperMonster003 on May 24, 2024.
*/
@Suppress("unused", "PropertyName", "PrivatePropertyName")
class ScriptRuntime private constructor(builder: Builder) {
@@ -186,6 +185,8 @@ class ScriptRuntime private constructor(builder: Builder) {
private val mJob = SupervisorJob()
val coroutineScope = CoroutineScope(Dispatchers.Default + mJob)
val coroutineContext = coroutineScope.coroutineContext
val ownerId = "runtime@${System.identityHashCode(this)}"
private var mUiHandlerAppContext: Context
private var mRootShell: AbstractShell? = null
@@ -601,7 +602,12 @@ class ScriptRuntime private constructor(builder: Builder) {
// ! 清空无障碍事件.
// ! en-US (translated by SuperMonster003 on Jul 29, 2024):
// ! To clear accessibility event callbacks.
ignoresException({ AccessibilityService.clearAccessibilityEventCallback() })
// @Hint by SuperMonster003 on Dec 29, 2025.
// ! Only clean up accessibility event callbacks registered
// ! by the current script to avoid affecting other still-running scripts.
// ! zh-CN: 只清理当前脚本注册的无障碍事件回调,避免影响其他仍在运行的脚本.
// # ignoresException({ AccessibilityService.clearAccessibilityEventCallback() })
ignoresException({ automator.removeAllEventsForThisRuntime() })
ignoresException({ RootUtils.resetRuntimeOverriddenRootModeState() })