Fix weekly review regressions#617
Conversation
kasnder
left a comment
There was a problem hiding this comment.
Verdict: Two of the three fixes (Slovenian string, tracker-protection defaults) are correct, complete, and well-tested. The onRevoke() change is the substantive one: it reverses a heavily-documented prior decision, and its correctness hinges on undocumented isAlwaysOn()/onRevoke() timing — validate on-device before merge.
Should fix
ServiceSinkhole.java:onRevoke()(~L3211-3240 in diff): #526 recovery ("always-on VPN turns itself off after 3-5 days") now depends entirely onisAlwaysOn()returningtrueduring the transient OS teardown/re-auth revoke. The ordering of when Android clears/reasserts the always-on designation relative to theonRevoke()callback is undocumented; if it readsfalsemid-teardown,resolveEnabledAfterRevokepersistsenabled=falseand TC self-disables permanently with no recovery — the exact failure the removed comment was written to prevent. There is no test or device evidence that the flag holds. Suggest validating a real #526-style always-on teardown on-device, and/or only clearingenabledwhen you positively observe another package holding the always-on slot rather than treating "not always-on" as authoritative. (unverified)ServiceSinkhole.java:onRevoke()pre-Q branch:minSdkis 23, so API 23-28 devices take theSettings.Secure.getString(cr, "always_on_vpn_app")path. That key is a@hidesetting; on some OEM/OS builds it can returnnulleven when TC is the configured always-on VPN, which disables TC on every revoke for those users. Confirm the key is reliably readable, or treatnullas "unknown → keep enabled" instead of "not always-on → disable." (unverified)
Nits
- The stated goal of honoring a deliberate system-settings disable symmetrically depends on
isAlwaysOn()returningfalseat revoke time; if the always-on designation lingers momentarily, the watchdog reclaims the slot and TC comes back on — defeating the intent. Worth an inline note that both directions rest on the same timing assumption. VpnRevokePolicyTestusesRobolectricTestRunnerto exercise a pure boolean function. That is justified (loadingServiceSinkhole's static footprint under plain JUnit would throw), but the test only touchesresolveEnabledAfterRevoke— no behavior change needed, just flagging it as heavier than it looks.
What's good
BlockingMode.resolveTrackerProtection(L88-98) is a clean, surgical fix: verified against the oldisMinimalMode(c) || getBoolean(pkg, true)logic, every no-explicit-pref default is preserved (non-browser → on, browser → off) and only the minimal-mode case that silently overrode an explicitfalseis corrected — so bulk "Exclude all apps" now sticks. Covered by a tidy truth-table test.- Slovenian
msg_wg_mullvad_countries_failednow carries the%splaceholder matching the base string (values/strings.xml:233), fixing both the blank message and the lint failure. - Both behavioral fixes ship with focused regression tests, and the extracted
statichelpers make the policy testable without Android state.
Automated review by Claude Opus 4.8 (agent-assisted).
The pre-Q revoke path reads the @hide "always_on_vpn_app" Settings.Secure key, which returns null on some OEM/OS builds even when TC is the configured always-on VPN. Treating that null (or a throwing lookup) as "not always-on" would persist enabled=false and permanently self-disable TC on every revoke for those users. Make the always-on lookup nullable and have resolveEnabledAfterRevoke keep the existing enabled state when the always-on designation is unknown, only persisting a disable when always-on is positively known to be off. Positively-known true/false behavior is unchanged. Adds truth- table tests for the null/unknown case and documents the timing assumption inline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Review update (2026-07-11): mostly ready, but validate the revoke policy on a real device before merging. CI is green and the Minimal-mode preference fix plus its regression coverage look correct. The follow-up also correctly treats an unreadable pre-Q always-on setting as unknown instead of forcibly disabling the service. The remaining risk is Android timing: correctness still assumes the always-on designation is authoritative inside |
Summary
onRevoke()only when Android still designates TrackerControl as the always-on VPN%splaceholderRoot causes and impact
Minimal mode previously returned
truefor every non-browser app before consulting the stored per-package preference. The bulk “Exclude all apps” action therefore appeared to succeed while most apps stayed protected.The revoke handler kept the global enabled preference set for every revoke. Since the watchdog restarts an enabled service when its VPN descriptor is absent, a deliberate system-settings revoke or another VPN taking the slot could cause TrackerControl to reclaim it. The new behavior retains recovery only for a still-configured always-on VPN.
The Slovenian
msg_wg_mullvad_countries_failedoverride was empty and omitted the formatting placeholder, producing a blank error and failing Android lint.Validation
./gradlew --no-daemon testFdroidDebugUnitTest lintFdroidDebuggit diff --check