Replace the three per-app toggles with one protection state - #729
Merged
Conversation
kasnder
marked this pull request as ready for review
August 20, 2026 10:28
Exclude from VPN, Tracker Protection and Internet Access were three booleans giving eight combinations of which only four are meaningful, with the dependency structure invisible: when Exclude was on the other two were inert but still rendered. Worse, in Minimal mode — the default, and forced on Play — the Tracker Protection row only rendered for browsers, so an ordinary app had no way to stay in the tunnel with blocking turned off. The levers were "bypass TrackerControl entirely" and "no internet at all". Replace all three with one four-option radio group over the same three stores, so no migration is needed: Protected, Trackers allowed, No internet, Bypass TrackerControl. AppProtectionState.resolve() derives the state and is total over all eight combinations, because the stores are written by more actors than the two UI paths — bulk settings actions, the beta vpn_exclude migration, mode exclusion sync and XML import can all leave arbitrary combinations on disk. Bypass takes precedence for a mechanical reason: an excluded app is handed to addDisallowedApplication, so the per-UID internet block cannot be enforced for it. Two defects found while wiring this up: The internet blocklist was only ever persisted from DetailsActivity's onPause, so the main-list icon toggle never persisted at all and the details screen tore on process death. InternetBlocklist now writes through on block/unblock, with the onPause save kept as a backstop. Uninstall cleanup also never dropped the UID's block, so a reinstall came back silently blocked; it now does, once no package remains in that UID. clearAutoExcludedApp was called unconditionally from both UI paths, so toggling an auto-excluded app off and on again converted a Minimal-mode auto-exclusion into a permanent one. It is now gated on the resulting apply being true, at those two sites only — the uninstall call site is correct as it stands. The main list read tracker protection differently from the details screen (apply && tracker_protect outside Minimal, apply alone inside), which denied the Internet toggle to any app with protection off. Both now read the shared state model. Copy is fresh for all four states and hand-translated into all 35 locales. It says trackers are neither blocked nor recorded, because tracker_protect=false gates updateAccess as well as blockKnownTracker — the old wording promised monitoring that does not happen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kasnder
force-pushed
the
claude/per-app-protection-vpn-routing-42bb96
branch
from
August 20, 2026 10:56
5568729 to
8c27aa4
Compare
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.
Exclude from VPN, Tracker Protection and Internet Access were three
booleans giving eight combinations of which only four are meaningful,
with the dependency structure invisible: when Exclude was on the other
two were inert but still rendered. Worse, in Minimal mode — the default,
and forced on Play — the Tracker Protection row only rendered for
browsers, so an ordinary app had no way to stay in the tunnel with
blocking turned off. The levers were "bypass TrackerControl entirely"
and "no internet at all".
Replace all three with one four-option radio group over the same three
stores, so no migration is needed: Protected, Trackers allowed, No
internet, Bypass TrackerControl. AppProtectionState.resolve() derives
the state and is total over all eight combinations, because the stores
are written by more actors than the two UI paths — bulk settings
actions, the beta vpn_exclude migration, mode exclusion sync and XML
import can all leave arbitrary combinations on disk. Bypass takes
precedence for a mechanical reason: an excluded app is handed to
addDisallowedApplication, so the per-UID internet block cannot be
enforced for it.
Two defects found while wiring this up:
The internet blocklist was only ever persisted from DetailsActivity's
onPause, so the main-list icon toggle never persisted at all and the
details screen tore on process death. InternetBlocklist now writes
through on block/unblock, with the onPause save kept as a backstop.
Uninstall cleanup also never dropped the UID's block, so a reinstall
came back silently blocked; it now does, once no package remains in
that UID.
clearAutoExcludedApp was called unconditionally from both UI paths, so
toggling an auto-excluded app off and on again converted a Minimal-mode
auto-exclusion into a permanent one. It is now gated on the resulting
apply being true, at those two sites only — the uninstall call site is
correct as it stands.
The main list read tracker protection differently from the details
screen (apply && tracker_protect outside Minimal, apply alone inside),
which denied the Internet toggle to any app with protection off. Both
now read the shared state model.
Copy is fresh for all four states and hand-translated into all 35
locales. It says trackers are neither blocked nor recorded, because
tracker_protect=false gates updateAccess as well as blockKnownTracker —
the old wording promised monitoring that does not happen.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Stack created with GitHub Stacks CLI • Give Feedback 💬