Make filtered-but-hidden apps reachable via search (#420, #483)#611
Merged
Conversation
Some installed apps are routed through the tun and filtered/blocked by TC (blocking is keyed on UID in ServiceSinkhole.isAddressAllowed / blockKnownTracker), yet never appear in the app list, so the user cannot open them to add an exception or exclude them. The only workaround is disabling TC entirely. Root cause: Rule.getRules(false, ...) applies the show_system / show_frozen / show_nointernet display filters when building the visible list, but the packet path filters by UID regardless of those display filters. An app hidden by a display filter (e.g. an OEM-preinstalled package that TC routes because "include system apps" is enabled, a frozen package, or a shared-UID sibling — #229) is therefore still blocked while being absent from the list. AdapterRule's search only searched the already-filtered visible subset (listAll), so searching for the hidden app could never find it either — matching the exact failing repro in #420 (search "Instagram" -> missing). Fix (search-only, keeps the default view uncluttered): - Rule.getSearchStubs(): build a lightweight, side-effect-free list of rules for every installed app (skips tracker counts, default seeding, sorting, snackbars). - AdapterRule: search now runs over the visible list plus every other installed app (lazily built, cached, invalidated on set()), so a hidden-but-filtered app can be found and opened to configure an exception. Visible apps keep their fully-populated rules. Empty query still shows exactly the visible list, so no clutter regression. Deferred (documented, not fixed here): the underlying display-filter / shared-UID (#229) attribution behaviour is left intact; this change only guarantees recoverability, which is the user-facing breakage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kasnder
commented
Jul 12, 2026
kasnder
left a comment
Member
Author
There was a problem hiding this comment.
Review finding: the new case-insensitive search path used the device default locale for toLowerCase(). In locales with special case mappings (notably Turkish), ASCII package names and user queries can normalize differently, causing hidden apps to remain undiscoverable. I fixed all three normalizations to use Locale.ROOT and verified :app:compileGithubDebugJavaWithJavac.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Some installed apps are filtered/blocked by TC but never appear in TC's app list, so the user cannot open them to add a per-tracker exception or exclude the app. The only workaround is disabling TC entirely — the worst kind of (unescapable) breakage.
Root cause
Blocking is keyed on UID in the packet path (
ServiceSinkhole.isAddressAllowed/blockKnownTracker/shouldTrackApp), and VPN routing is decided fromRule.getRules(true, ...)(the full app set, gated only by!apply || (!includeSystem && system)).The visible list, however, is built by
Rule.getRules(false, ...), which applies theshow_system/show_frozen/show_nointernetdisplay filters. So an app hidden by one of those filters — e.g. an OEM-preinstalled package that TC routes because "include system apps in VPN" is enabled, a frozen package, or a shared-UID sibling (#229) — is still routed and blocked by UID while being absent from the list.AdapterRule's search only iterated the already-filtered visible subset (listAll), so the hidden-but-filtered app could not be found by search either. That matches #420's exact repro (search "Instagram" → missing).Fix (search-only; default view unchanged)
Rule.getSearchStubs(context)— a lightweight, side-effect-free list of rules for every installed app (skips tracker-count queries, default seeding, sorting, snackbars). Enough to be matched by search and to open the per-app details screen.AdapterRule— search now runs over the visible list plus every other installed app (pool built lazily, cached, invalidated onset()). A hidden-but-filtered app can now be found and opened to configure an exception or exclude it. Visible apps keep their fully-populated rules (tracker counts intact). An empty query still shows exactly the visible list, so there is no clutter regression in the default view.This keeps the recovery path (search → open → configure/exclude) working for any app TC can filter, without adding user-facing toggles (per the project's simplicity-over-configurability philosophy).
Deferred (documented, not fixed here)
The underlying display-filter behaviour and shared-UID runtime attribution (#229 — the packet path resolves a UID to
getPackagesForUid(uid)[0], so per-package settings on a non-representative sibling may not take effect at runtime) are left intact. This change guarantees reachability/recoverability, which is the user-facing breakage; a fuller shared-UID attribution redesign is out of scope here.Testing
./gradlew :app:compileGithubDebugJavaWithJavac— BUILD SUCCESSFUL (RustwgbridgeBuildvia preBuild ran;cargopresent).🤖 Generated with Claude Code