From 3ee4926bf6f574aff9d0741f386c5a47f68a279e Mon Sep 17 00:00:00 2001 From: undisputedP Date: Sat, 9 May 2026 16:59:28 +0530 Subject: [PATCH] v1.0.8: throttle accessibility tree walks to fix frame-rate drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reported visible frame-rate drop while scrolling YouTube with the accessibility service active. Cause: YouTube fires TYPE_WINDOW_CONTENT_CHANGED at ~30 Hz during scroll, and v1.0.7 ran the full 1200-node tree walk on every event. That's ~36k node inspections per second while scrolling — enough to push the system past the 16ms frame budget. Two changes: 1. Throttle walks for content-changed events to one every 250ms. State-changed events (rare; fire when entering Shorts / navigating screens) keep being processed immediately so detection latency on the path that matters is unchanged. 2. Reduce MAX_NODES_PER_WALK from 1200 to 600. Empirically the relevant Shorts UI lives in the first few hundred nodes; the 1200 cap was just safety margin we don't need. Net effect for the user: while on YouTube home / scrolling the feed, we walk ~4×/sec instead of ~30×/sec, and each walk visits half as many nodes. Total CPU work in the steady state is about 1/15th of v1.0.7. Detection on Shorts open is unaffected since that triggers a STATE_CHANGED event which still walks immediately. Bump versionCode 8 → 9, versionName 1.0.7 → 1.0.8. Co-Authored-By: Claude Opus 4.7 --- ShortsBlocker/app/build.gradle | 4 ++-- .../service/ShortsAccessibilityService.kt | 24 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ShortsBlocker/app/build.gradle b/ShortsBlocker/app/build.gradle index 6aa4f02..fa1cd07 100644 --- a/ShortsBlocker/app/build.gradle +++ b/ShortsBlocker/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.shortsBlocker" minSdk 21 targetSdk 34 - versionCode 8 - versionName "1.0.7" + versionCode 9 + versionName "1.0.8" } buildTypes { diff --git a/ShortsBlocker/app/src/main/java/com/shortsBlocker/service/ShortsAccessibilityService.kt b/ShortsBlocker/app/src/main/java/com/shortsBlocker/service/ShortsAccessibilityService.kt index 3c9d103..aa32d6b 100644 --- a/ShortsBlocker/app/src/main/java/com/shortsBlocker/service/ShortsAccessibilityService.kt +++ b/ShortsBlocker/app/src/main/java/com/shortsBlocker/service/ShortsAccessibilityService.kt @@ -43,7 +43,14 @@ class ShortsAccessibilityService : AccessibilityService() { companion object { const val TAG = "ShortsA11y" private const val MIN_DISMISS_INTERVAL_MS = 600L - private const val MAX_NODES_PER_WALK = 1200 + // Cap how often we walk the tree for content-changed events. + // YouTube fires TYPE_WINDOW_CONTENT_CHANGED at ~30 Hz during + // normal scrolling; without throttle we ate enough CPU to drop + // user-visible frame rate. State-changed events (entering Shorts, + // navigating screens) bypass this throttle so detection latency + // stays low when it matters. + private const val MIN_CONTENT_WALK_INTERVAL_MS = 250L + private const val MAX_NODES_PER_WALK = 600 // STRONG view-ID suffixes (matches ":id/" exactly). private val STRONG_VIEW_ID_SUFFIXES = setOf( @@ -93,6 +100,7 @@ class ShortsAccessibilityService : AccessibilityService() { } private var lastDismissAt: Long = 0L + private var lastWalkAt: Long = 0L override fun onAccessibilityEvent(event: AccessibilityEvent?) { if (event == null) return @@ -102,11 +110,21 @@ class ShortsAccessibilityService : AccessibilityService() { val pkg = event.packageName?.toString() ?: return if (!pkg.contains("youtube", ignoreCase = true)) return + val now = System.currentTimeMillis() lastPackage = pkg lastEventType = AccessibilityEvent.eventTypeToString(event.eventType) - lastEventAtMs = System.currentTimeMillis() + lastEventAtMs = now - if (lastEventAtMs - lastDismissAt < MIN_DISMISS_INTERVAL_MS) return + // Recently dismissed → don't even consider another walk. + if (now - lastDismissAt < MIN_DISMISS_INTERVAL_MS) return + + // State changes (rare, fire on screen transitions like opening + // Shorts) always processed. Content changes (very frequent) get + // throttled so we don't burn CPU on every scroll tick. + val isStateChange = event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + if (!isStateChange && now - lastWalkAt < MIN_CONTENT_WALK_INTERVAL_MS) return + + lastWalkAt = now val root = rootInActiveWindow ?: return try {