Skip to content

Fix ANR when handling package add/remove broadcasts#678

Merged
kasnder merged 1 commit into
masterfrom
claude/bug-fix-b4zu56
Jul 23, 2026
Merged

Fix ANR when handling package add/remove broadcasts#678
kasnder merged 1 commit into
masterfrom
claude/bug-fix-b4zu56

Conversation

@kasnder

@kasnder kasnder commented Jul 23, 2026

Copy link
Copy Markdown
Member

Problem

An ANR was reported in DetailsActivity (Input dispatching timed out … Waited 5000ms). The captured thread dump shows a lock-contention stall, not a hang in the activity itself:

  • main thread — blocked in Rule.clearCache() (Rule.java:201), called synchronously from packageChangedReceiver.onReceive() (ServiceSinkhole.java:2827), waiting to lock the ApplicationEx monitor.
  • "TrackerControl command" threadholds that monitor inside Rule.getRules() and is parked in a slow PackageManager.getPackageInfo() binder IPC (via Util.isSystemRule.<init> while building the rule list).
  • AsyncTask Support for Android 10 missing #1 — also waiting on the same monitor in Rule.getRules().

Both Rule.getRules() and Rule.clearCache() synchronize on context.getApplicationContext(). getRules() makes per-package PackageManager IPC while holding the lock; when that IPC is slow, the command thread keeps the monitor for several seconds. Because the package-changed broadcast receiver ran clearCache() directly on the main thread, the main thread blocked on that monitor, input dispatch stalled, and the system raised an ANR.

Fix

ServiceSinkhole.java only:

  • The packageChangedReceiver now hands its work to the existing executor via goAsync(), so waiting on the application-context monitor happens on a background thread and never blocks the UI. The receiver body was extracted into handlePackageChanged(context, intent).
  • Both callers of notifyNewApplication() now run off the main thread, so the nested goAsync()/new Thread() inside the tracker-library check (checkTrackers) is no longer needed — it runs inline on the caller's background thread. Calling goAsync() twice would have returned null on the second call, so this also keeps the async handoff correct.
  • notifyNewApplication(int, BroadcastReceiver) became notifyNewApplication(int, boolean) — the BroadcastReceiver argument existed only to feed that nested goAsync(). The notification-only caller in set() passes false; the install path passes true.

No behavioral change to what work happens on package install/removal — only which thread it runs on.

Testing

The full Gradle build could not run in this environment: the pinned Gradle 9.6.1 distribution and AGP 9.3.0 are blocked by the sandbox egress policy (GitHub returns 403 and nothing is cached for offline mode). The change is a localized, self-contained refactor within ServiceSinkhole.java; a ./gradlew :app:compileGithubDebugJavaWithJavac on a networked checkout is recommended before merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_018yc5s4aH1mWrYw4gpsHRrZ


Generated by Claude Code

The package changed broadcast receiver ran Rule.clearCache() and its
follow-up work directly on the main thread. clearCache() synchronizes on
the application context, the same lock the command thread holds while
building the rule list via getRules(), which in turn issues per-package
PackageManager IPC. When those binder calls are slow, the command thread
holds the lock for several seconds and the main thread blocks in
clearCache(), stalling input dispatch and triggering an ANR.

Move the receiver's work off the main thread with goAsync() plus the
existing executor, so waiting on the lock no longer blocks the UI. Since
both callers of notifyNewApplication() now run off the main thread, drop
the nested goAsync()/Thread in the tracker-library check and run it
inline, and replace the BroadcastReceiver parameter with a boolean flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018yc5s4aH1mWrYw4gpsHRrZ
@kasnder
kasnder marked this pull request as ready for review July 23, 2026 15:59
@kasnder
kasnder merged commit 9a75a75 into master Jul 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants