From ec29e3469a571e817a610e6e2c173d6849b0d197 Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:13:06 +0200 Subject: [PATCH 1/3] Add open-issue triage checklist + session outcome (WI-1 merged; #602-#612 draft) Code-grounded fixability review of the open-issue backlog, per-item work items with root causes and PRs, unfixable/decline buckets, and reply guidance. Co-Authored-By: Claude Opus 4.8 --- OPEN_ISSUES_TRIAGE.md | 86 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/OPEN_ISSUES_TRIAGE.md b/OPEN_ISSUES_TRIAGE.md index 3603e9df8..c12618083 100644 --- a/OPEN_ISSUES_TRIAGE.md +++ b/OPEN_ISSUES_TRIAGE.md @@ -3,6 +3,25 @@ **Generated:** 2026-07-11 · **Reviewed against:** `CLAUDE.md` design philosophy + `TODO.md` standing constraints **Method:** Every item below was traced into the actual code (Java + native C + Rust gotatun). Line numbers are from the tree at/around `master` (`a9f060a`) — re-verify before editing, code moves. +## Status (live) + +Draft PRs are being generated by agents. This table is the running checklist — ✅ = draft PR open. + +| WI | Issue(s) | State | PR | +|----|----------|-------|----| +| WI-1 | #327 pause auto-resume | ✅✅ MERGED | [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) | +| WI-2 | #576 dead toggle | ✅ draft PR open | [#602](https://github.com/TrackerControl/tracker-control-android/pull/602) | +| WI-3 | #478 PMTUD MSS clamp | ✅ draft PR open | [#603](https://github.com/TrackerControl/tracker-control-android/pull/603) | +| WI-4 | #339 IPv6 DNS | ✅ draft PR open | [#604](https://github.com/TrackerControl/tracker-control-android/pull/604) | +| WI-5 | #526/#331 self-disable | ✅ draft PR open | [#605](https://github.com/TrackerControl/tracker-control-android/pull/605) | +| WI-6 | #554/#561 transparency (#358 deferred) | ✅ draft PR open | [#607](https://github.com/TrackerControl/tracker-control-android/pull/607) | +| WI-7+8 | #586/#575/#551/#560/#559 UI polish | ✅ draft PR open | [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) | +| WI-9 | #405 memory footprint | ✅ draft PR open | [#612](https://github.com/TrackerControl/tracker-control-android/pull/612) | +| WI-11 | general self-check / diagnostics (new) | ✅ draft PR open | [#608](https://github.com/TrackerControl/tracker-control-android/pull/608) | +| WI-12 | #483/#420 missing apps (filtered but unlisted) | ✅ draft PR open | [#611](https://github.com/TrackerControl/tracker-control-android/pull/611) | +| WI-13 | #390 collapsible tracker categories (+ clear app settings) | ✅ draft PR open | [#610](https://github.com/TrackerControl/tracker-control-android/pull/610) | +| WI-14 | #308 grayed-out advanced/backup options | ✅ draft PR open | [#609](https://github.com/TrackerControl/tracker-control-android/pull/609) | + ## How to use this document Each **work item (WI-n)** in Sections A/B is self-contained: one agent can be handed a single WI and act on it without re-reading the whole review. Each carries a **Verdict**, the **root cause with file:line**, a **concrete fix**, and a **risk/scope** note. @@ -20,16 +39,18 @@ Verdict vocabulary: ## Section A — Ready-to-implement fixes (Fixable-aligned) -### WI-1 · Pause auto-resumes after a manual resume, dropping connections +### WI-1 · Pause auto-resumes after a manual resume, dropping connections — ✅✅ MERGED [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) - **Issue:** [#327](https://github.com/TrackerControl/tracker-control-android/issues/327) +- **Status:** Draft PR #601 open. Fix in `ActivityMain.onActivityResult` (REQUEST_VPN branch): cancel the `INTENT_ON` pause alarm before `ServiceSinkhole.start(...)`, matching `WidgetAdmin`'s PendingIntent (the Quick-Settings tile `ServiceTileMain` already did this — confirms the main-UI path was the gap). `compileGithubDebugJavaWithJavac` green. Needs on-device pause→resume verification. - **Verdict:** Fixable-aligned. Smallest clean win on the board; no packet-path risk. - **Symptom:** User pauses TC for 10 min from the notification, then manually resumes from the app. Later the original pause timer still fires — TC vibrates, a second foreground/VPN re-init happens mid-session, and in-flight connections drop (one reporter lost a payment). - **Root cause:** A pause schedules an `INTENT_ON` alarm `pause` minutes out in `WidgetAdmin.java:84-90`. That alarm is only cancelled inside `WidgetAdmin.onReceive` for its three broadcast actions (`WidgetAdmin.java:57-61`). The **main-UI resume path does not go through that code** — `ActivityMain.java:282-355` sets `enabled=true` and calls `ServiceSinkhole.start(...)` directly, never calling `am.cancel(...)`. The orphaned alarm survives and re-fires. - **Fix:** In the UI resume path (`ActivityMain`), cancel the pending `INTENT_ON` pause alarm (mirror the cancel in `WidgetAdmin.java:57-61`), or make the alarm handler a no-op when TC is already enabled (idempotent resume). - **Risk/scope:** Tiny, UI/lifecycle only. No native or DNS change. -### WI-2 · Some tracker domains silently won't toggle ALLOWED→BLOCKED (regression in 2026.04.03) +### WI-2 · Some tracker domains silently won't toggle ALLOWED→BLOCKED (regression in 2026.04.03) — ✅ Draft PR [#602](https://github.com/TrackerControl/tracker-control-android/pull/602) - **Issue:** [#576](https://github.com/TrackerControl/tracker-control-android/issues/576) (confirmed by 3+ users: Android 11 rooted, Android 16, F-Droid) +- **Status:** Draft PR #602 open. UI-only fix in `TrackersListAdapter`: override `isEnabled(position)`/`areAllItemsEnabled()` so ambiguous shared-IP rows are non-interactive at the ListView level (standard idiom), plus a defensive no-op guard + informative toast, and a new `allowed_shared_ip` string ("Allowed — shared IP; enable Strict mode to block"). Runtime blocking semantics untouched. `compileGithubDebugJavaWithJavac` green. Needs visual confirmation. - **Verdict:** Fixable-with-tension leaning aligned — the **runtime** behaviour is correct-by-design; the **UI** lies to the user. Fix the UI, not the blocking logic. - **Symptom:** In an app's tracker list (Standard mode), tapping certain domains (e.g. an Amazon ad domain under gasbuddy) does nothing — the row won't flip to BLOCKED — while other rows in the same enabled section toggle fine. - **Root cause:** The per-row status is recomputed in `TrackersListAdapter.java:305-318`. For domains where `t.isAllowedInStandardMode()` is true, it force-pins the label back to ALLOWED in non-strict mode: @@ -46,8 +67,9 @@ Verdict vocabulary: - *"Old config export won't import":* Export/import is package-based (`ActivitySettings.java:1246-1315` export, `1493-1554` import). The reporter did **new→old (downgrade)** import, which is inherently unsupported. *Fix worth doing:* stamp exports with a format/version and warn on partial import; ensure old→new (upgrade) UID remapping is robust. - **Risk/scope:** UI only for the core toggle fix. Do **not** change the shared-IP runtime semantics (that's deliberate, `TODO.md`). -### WI-3 · Sites black-hole over tethering (PMTUD ICMP dropped) +### WI-3 · Sites black-hole over tethering (PMTUD ICMP dropped) — ✅ Draft PR [#603](https://github.com/TrackerControl/tracker-control-android/pull/603) - **Issues:** [#478](https://github.com/TrackerControl/tracker-control-android/issues/478) (also explains part of [#562](https://github.com/TrackerControl/tracker-control-android/issues/562)) +- **Status:** Draft PR #603 — implemented the safer MSS-clamp option (`TCP_MSS_CLAMP 1360` in `netguard.h`; clamp `s->tcp.mss` in `tcp.c handle_tcp` and the injected SYN/SYN-ACK in `write_tcp` to `min(mss,1360)`). Also fixed a **latent byte-order bug**: the SYN/SYN-ACK MSS was written in host order without `htons()` (harmless at ~9960, but would corrupt any real clamp). `externalNativeBuildGithubDebug` green. Left `get_mtu`/`setMtu` and full ICMP-passthrough as documented follow-ups. Needs on-device tethering validation. - **Verdict:** Fixable-aligned, battery-neutral. Strong recover-from-breakage value. - **Symptom:** With a laptop tethered through the phone, large flows (TLS handshakes, GitHub) hang/time out while small flows work. The phone's own apps are unaffected. Reporter found the removed "Allow LAN access" toggle used to fix it. - **Root cause:** `handle_icmp()` drops **every** ICMP that isn't an echo — `icmp.c:166-170` (`if (icmp->icmp_type != ICMP_ECHO) { log "...not supported"; return 0; }`). This discards ICMPv4 type 3 code 4 (**Fragmentation Needed**) and ICMPv6 type 2 (**Packet Too Big**) — the messages Path-MTU-Discovery relies on. Compounded by a hardcoded oversized tun: `get_mtu()` returns **10000** (`ip.c:58-59`), MSS derived ≈ **9960** (`ip.c:62-67`; this is the `mss 9960` in the reporter's log), `setMtu(10000)` at `ServiceSinkhole.java:1628,1645`. A tethered client (MTU 1500) on a smaller path MTU never learns to shrink → large packets black-hole. The phone's own apps are immune because their sockets are re-originated over a real kernel socket that does PMTUD itself. The reporter's log literally shows `ICMP type 3 code 4 ... not supported` repeated. The old LAN-access workaround pulled the tether subnet out of the tun; those toggles were removed in PR #546 (only orphan strings remain, `strings.xml:110-112`; `VpnRoutes.java:43-70` now excludes RFC1918/CGNAT unconditionally), so the ICMP drop is now load-bearing. @@ -56,24 +78,27 @@ Verdict vocabulary: 2. **Pass PMTUD ICMP through:** translate/inject ICMPv4 type 3 code 4 and ICMPv6 type 2 back into the tun in `handle_icmp` (`icmp.c:166`). - **Risk/scope:** Native packet path. Battery-neutral (no new wakeups). Test tethering + normal on-device browsing; verify no regression to ICMP echo handling. -### WI-4 · DNS fails on IPv6-only mobile data (Android 13+) +### WI-4 · DNS fails on IPv6-only mobile data (Android 13+) — ✅ Draft PR [#604](https://github.com/TrackerControl/tracker-control-android/pull/604) - **Issues:** [#339](https://github.com/TrackerControl/tracker-control-android/issues/339), mobile-data half of [#449](https://github.com/TrackerControl/tracker-control-android/issues/449) +- **Status:** Draft PR #604. Conservative two-pass `getDns()`: pass 1 = IPv4-only (byte-for-byte unchanged output on IPv4/dual-stack); pass 2 = IPv6, only when pass 1 is empty AND the `ip6` pref is on (the IPv6-only case). Traced downstream (builder already routes `2000::/3` + gates DNS on `ip6`; native `udp.c`/`dns.c` parse IPv6 DNS fine) — no native change needed. Compiles green. Needs IPv6-only-SIM / NAT64 validation. - **Verdict:** Fixable-with-tension. Real correctness gap, not a platform limit. - **Symptom:** All resolution fails on mobile data but works on dual-stack Wi-Fi; common on modern IPv6-only cellular. - **Root cause:** `getDns()` rejects every non-IPv4 resolver — `ServiceSinkhole.java:1337,1346,1360` each gate on `dns instanceof Inet4Address`. On IPv6-only cellular the carrier's IPv6 DNS servers are all discarded; it falls through to hardcoded IPv4 public DNS (`getDefaultDns`, `ServiceSinkhole.java:1430-1443`), which is unreachable without a working CLAT/NAT64 path. (#478's reporter also noted the carrier "enabled some IPv6 DNS recently.") - **Fix:** Accept IPv6 resolvers in `getDns` and add them as tun DNS when `ip6` is enabled. - **Risk/scope:** The native path and some assumptions are IPv4-centric — needs care and on-device testing on an IPv6-only SIM. Tension is implementation, not policy. -### WI-5 · TC self-disables after OS-initiated VPN teardown (no auto-recovery) +### WI-5 · TC self-disables after OS-initiated VPN teardown (no auto-recovery) — ✅ Draft PR [#605](https://github.com/TrackerControl/tracker-control-android/pull/605) - **Issues:** [#526](https://github.com/TrackerControl/tracker-control-android/issues/526), [#331](https://github.com/TrackerControl/tracker-control-android/issues/331) +- **Status:** Draft PR #605. `onRevoke()` no longer persists `enabled=false`, letting OS always-on restart / `ReceiverAutostart` / watchdog recover. Verified all three in-app disable paths set `enabled=false` themselves before stopping (and tearing down our own tunnel doesn't fire `onRevoke`), so a revoke reaching that method is always externally initiated → safe. Anti-thrash guard (`StartFailedException` path) left intact for #331. Documented tradeoff: a user disabling via *system* VPN settings will now see TC recover. Needs multi-day always-on device testing. - **Verdict:** Fixable-with-tension. Android can't tell you *why* revoke fired. - **Symptom:** Always-on VPN "turns itself off after 3–5 days"; user gets Android's "VPN disconnected" notice and must manually re-enable. #331: enabling another VPN permanently disables TC. - **Root cause:** `onRevoke()` unconditionally persists `enabled=false` (`ServiceSinkhole.java:3147-3152`); the start/reload error path does the same when `VpnService.prepare() != null` (`ServiceSinkhole.java:537-554`). Service is `START_STICKY` (`3088,3122`) but `onStartCommand` reads `enabled` (`3096,3113-3114`) and, being false, immediately stops — so an OS-initiated teardown (Doze, memory pressure, update, re-auth of the always-on slot) is treated identically to a deliberate user disable, with no self-recovery. - **Fix:** Don't permanently clear `enabled` on revoke. Let `ReceiverAutostart`/watchdog re-establish when the always-on slot is reacquired. Accept a small "did the user really mean off?" ambiguity in exchange for always-on reliability. - **Risk/scope:** Lifecycle logic; test genuine user-disable still works and there's no restart storm when another VPN legitimately takes the slot (#331). -### WI-6 · Transparency: blocked connections invisible / unlabelled in logs +### WI-6 · Transparency: blocked connections invisible / unlabelled in logs — ✅ Draft PR [#607](https://github.com/TrackerControl/tracker-control-android/pull/607) (visibility half; #358 deferred) - **Issues:** [#554](https://github.com/TrackerControl/tracker-control-android/issues/554), [#561](https://github.com/TrackerControl/tracker-control-android/issues/561), [#358](https://github.com/TrackerControl/tracker-control-android/issues/358) (same fix) +- **Status:** Draft PR #607. Done: (#561) per-row coloured BLOCKED/ALLOWED label in `AdapterLog.bindView` mirroring `TimelineAdapter`; (#554) added `cloudflare.com` to `ignoreDomains` (verified exact registrable-domain match — leaves the `cloudflareinsights.com` beacon blockable). **Deferred** (#358/#554 per-app-unattributed rows): `getAppTrackers` skips hosts where `findTracker()==null` and the block decision is IP-based off the UID-global `dns` table (§C-3 attribution limit) — a real fix needs the deferred schema redesign, out of scope. #561/#560 labels need visual confirmation. - **Verdict:** Transparency half = **Fixable-aligned**; misattribution half = **Unfixable-by-construction** (see C-3). Scope this as *visibility only*. - **Symptom:** A domain is blocked but doesn't appear as a blockable entry in the app's tracker list (#554, #358 — "no way to unblock"); and the raw traffic log doesn't clearly say blocked vs allowed (#561). - **Root cause:** @@ -83,7 +108,7 @@ Verdict vocabulary: - **Fix:** Surface blocked/unattributed connections in the per-app view so users can make *targeted* exceptions (this is the recover-from-breakage carve-out that makes #358 in-scope). Add a text label/legend to the raw log. Add Cloudflare to shared-CDN handling. Do **not** attempt true per-app attribution here (see C-3). - **Risk/scope:** UI + a data-surfacing change; no packet-path risk. -### WI-7 · Memory leak: one real Fragment retention (rest are noise) +### WI-7 · Memory leak: one real Fragment retention (rest are noise) — ✅ Draft PR [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) (bundled with WI-8) - **Issue:** [#586](https://github.com/TrackerControl/tracker-control-android/issues/586) (LeakScope automated report, 16 findings) - **Verdict:** Fixable-aligned but **low impact** — will not move the #405 RAM numbers. - **Triage of the 16 findings:** @@ -92,8 +117,9 @@ Verdict vocabulary: - **Not leaks (6):** `ViewBindingOpportunity` are refactor suggestions with no memory semantics. Ignore. - **Risk/scope:** Trivial for the one real fix. -### WI-8 · Edge-to-edge & theming UI fixes (batchable) +### WI-8 · Edge-to-edge & theming UI fixes (batchable) — ✅ Draft PR [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) (bundled with WI-7) - **Issues:** [#575](https://github.com/TrackerControl/tracker-control-android/issues/575), [#551](https://github.com/TrackerControl/tracker-control-android/issues/551), [#560](https://github.com/TrackerControl/tracker-control-android/issues/560), [#559](https://github.com/TrackerControl/tracker-control-android/issues/559) +- **Status:** Draft PR #606 (also carries WI-7 #586 leak fix). #575 via `ViewCompat.setOnApplyWindowInsetsListener` on `VpnFragment` RecyclerView; #551 bottom-inset margin on onboarding nav buttons; #560 new `colorFooterLink` (brighter in night); #559 restored `trackDecorationTint` on the `RocketSwitch.NoIcon` style (log toggle only — main power switch untouched). Build green. #559/#560 need visual confirmation. - **Verdict:** Fixable-aligned, low effort. Can be one PR. - **Details:** - **#575** WireGuard/VPN screen last item under nav bar: `fragment_vpn.xml` RecyclerView has only `padding=16dp`, no window-inset handling. Apply bottom `WindowInsets` padding. Compare the correct `activity_wg_profiles.xml` (`clipToPadding=false` + `paddingBottom=80dp`). @@ -106,8 +132,9 @@ Verdict vocabulary: ## Section B — Fixable but needs a decision first / larger -### WI-9 · RAM footprint (~300 MB at rest, grows on settings changes) +### WI-9 · RAM footprint (~300 MB at rest, grows on settings changes) — ✅ Draft PR [#612](https://github.com/TrackerControl/tracker-control-android/pull/612) - **Issue:** [#405](https://github.com/TrackerControl/tracker-control-android/issues/405) +- **Status:** Draft PR #612. Landed only the behavior-preserving change: **stream-parse assets with `JsonReader`** (X-Ray/DDG from `InputStream`; Disconnect keeps its surrogate-safe reversed-string handling verbatim, streams via `StringReader`) — cuts the transient parse high-water on every reload. Verified byte-for-byte identical `domain→(name,category,country)` maps (4712 entries, `domain_based_blocking` on/off) + 109/109 unit tests green. **Not done:** per-domain `Tracker` collapse (unsafe — `getName()` returns `"dom (Company)"` feeding counts/UI; ~4.7k objects, not the 300 MB driver) and lazy GeoLite2 (already lazy). **Honest caveat:** assets are only ~88–162 KB, so real RAM impact needs on-device profiling — this is a modest churn win, not the root of the 300 MB. - **Verdict:** Partial-only / Needs-on-device-profiling. **Profile (hprof/LeakCanary) before chasing a "leak."** - **Analysis:** Dominated by in-memory blocklists in non-minimal mode. `loadTrackers` loads Disconnect + X-Ray + DDG when not minimal (`TrackerList.java:178-186`) into the static `hostnameToTracker` map (`TrackerList.java:64`); with `domain_based_blocking` (`preferences.xml:84`), `addTrackerDomain` allocates a **distinct `Tracker` per domain** (`TrackerList.java:593-599`) — a large object-count multiplier over tens of thousands of Disconnect domains. Plus 9.5 MB `GeoLite2-Country.mmdb`, 2.5 MB `hosts.txt`, and whole-file JSON parsing into `String` then `new JSONObject(...)` (Disconnect does `String → StringBuilder.reverse() → JSONObject`, ~3× transient peak, `TrackerList.java:524-534`). `reloadTrackerData` **does** clear correctly (`TrackerList.java:151-165`), so the growth-on-settings-change is most consistent with native-heap / RSS high-water churn from repeated full jni reloads (triggered from `ActivitySettings.java:933`, `ServiceSinkhole.java:1892`), not a classic Java reference leak. - **Fix options (all help the default user, no new toggles):** (1) stream-parse assets with `JsonReader` instead of whole-file `String`+`JSONObject`; (2) stop allocating a per-domain `Tracker` under `domain_based_blocking` — store the domain against a shared `Tracker`; (3) load `GeoLite2` lazily (only when the countries UI opens). @@ -118,6 +145,28 @@ Verdict vocabulary: - **Analysis:** No literal ~2.5 min timer exists anywhere. Two *continuous* CPU loops were added by the gotatun migration and run **only when remote egress is enabled**: gotatun's 250 ms `handle_timers` tokio loop (`gotatun-0.7.2 device/mod.rs:500-502`, spawned at `:219`) and TC's 1 s `WgConnectivityMonitor` poll calling `tunnel.stats()` over JNI (`WgConnectivityMonitor.kt:226`, `LOOP_SLEEP_MS=1000`). The ~2.5 min *radio* cadence is emergent WG re-handshake (gotatun `noise/timers.rs:30-31`, `REKEY_AFTER_TIME=120s`/`REJECT_AFTER_TIME=180s`) because screen-off drops persistent-keepalive (`WgConfig.kt:31-35`) — this is protocol-optimal and intentionally not "fixed" with a keepalive (would add its own wakeups). The restart-backoff defeat noted historically is **already fixed** (`restartAttempts` now resets on observed rx, `WgEgress.kt` `onRxAdvanced`). - **Fix:** When the tunnel is idle **and** screen-off, back off the 1 s `WgConnectivityMonitor` poll (extend its existing `onSuspended`/doze handling to e.g. 10–30 s) and consider a slower/adaptive gotatun tick. No change for default (no-egress) users — they have no such loop. +### WI-11 · General self-check / diagnostics (strategic — addresses the un-triageable-report problem) +- **Motivation:** "Apps don't connect" / "battery drain" reports are un-actionable because reporters never provide the deciding fact (Private DNS/DoT on? always-on VPN? blocking mode? remote egress? IPv6-only carrier?). Rather than chase each report, give the app an **on-demand self-check** that captures those facts itself and produces a plain-language verdict + shareable report. +- **Verdict:** Fixable-aligned (diagnostic, not configuration — fits the philosophy). Report-first core is low-risk (no packet-path, no background pollers). +- **Design constraints (maintainer steer):** Battery is *surfaced, not re-investigated* — surface TC's own wakelock/CPU/wakeup stats + the mis-attribution explanation (see C-1); do not re-derive. Connectivity diagnostics stay in the **non-WireGuard** path (the gotatun egress is already well-worked). On-demand only (battery). All data on-device; user chooses to share. +- **Heritage note:** NetGuard originally had diagnostic/network-info/log-export code; the maintainer believes it was **removed** from TC and is unsure it was ever sufficient for TC's DNS/tracker model. First step is git-history archaeology to recover it and assess the gap. +- **Signals the TC check must surface** (from Sections B/C + the connectivity map): Private DNS/DoT active (#348), IPv6-only DNS discarded (#339), always-on/onRevoke state (#526/#331), blocking mode + Strict shared-IP overblock (#297), system-apps/DoH/remote-egress toggles, tun-up / DNS-resolving / route sanity, and the battery attribution stats. +- **Status:** ✅ Draft PR [#608](https://github.com/TrackerControl/tracker-control-android/pull/608). **Heritage found:** NetGuard's "Technical information" screen was removed from TC in commit `b067615` (2020-09-08) — only the UI; the helpers (`Util.getNetworkInfo`/`isPrivateDns`/`getDefaultDNS`/`batteryOptimizing`/`isInternetWorking`…) still exist → reuse, not restore. **Verdict:** old NetGuard dump NOT sufficient for TC (generic firewall info, none of TC's tracker/DNS failure modes, no verdict). **Built:** on-demand `DiagnosticsReport` + `ActivityDiagnostics` (Connectivity + Battery verdict panels; surfaces DoT/#348, blocking mode + Strict overblock/#297, IPv4-only-DNS/#339, always-on/#526, system-app routing, DoH, egress, battery mis-attribution/C-1), Copy/Share, entry via "Run self-check" in the Troubleshooting dialog. Zero background cost; WG untouched; build green. **Deferred:** active auto-recovery/health-probing + real wakelock accounting. Needs on-device verdict-correctness confirmation. + +### WI-14 · Grayed-out advanced/backup settings (#308) — ✅ Draft PR [#609](https://github.com/TrackerControl/tracker-control-android/issues/609) +Per-item verdict (the report conflated three things): +- **Import/Append hosts** — not a current bug. Hosts mgmt was reworked into **Download hosts file** + **Manage blocklists** (`ActivityBlocklists`); `setting_hosts_append` etc. are orphan strings. The Minimal-mode visibility regression (blocking-mode commit `6946906` hid these in the default mode → fueled #570/#589 "removed" reports) was **already fixed by #547** (`81864a0`). No change needed. +- **Track/Reset network usage** — intentional dead control (`android:enabled="false"` since 2020; feature wired in `ServiceSinkhole`/`DatabaseHelper` but never enabled, no viewing UI). A permanently-grey row is pure confusion → **removed the two dead rows**. +- **Backup Export/Import** — **genuine bug**: gated by `resolveActivity(...) != null`, which returns null on ROMs without a resolvable SAF DocumentsUI → silently greys even though the picker would launch. **Fixed**: dropped the guard, keep enabled, catch `ActivityNotFoundException` with a Toast. +- **#308 close plan:** merge #609, then close noting hosts mgmt now lives under *Manage blocklists* / *Download hosts file* (resolved by #547). +- **Build caveat:** delivered before `BUILD SUCCESSFUL` confirmed (Rust `wgbridgeBuild` still running, 3 agents contending); Java+XML change (3 files, +16/−14) verified by inspection. + +### WI-13 · Collapsible tracker categories + clear-app-settings (#390) — ✅ Draft PR [#610](https://github.com/TrackerControl/tracker-control-android/pull/610) +Both parts landed. **Collapsible headers:** each category name in the tracker-details view is tappable to collapse/expand (chevron `ic_expand_less`/`ic_expand_more`; collapse state in an in-adapter `Set`, session-only, not persisted; hides the list + tip + uncertainty note). Declined the issue's *global default expand/collapse setting* (config proliferation). **Clear app settings:** new overflow item in `DetailsActivity` behind a confirm dialog, reusing `TrackerBlocklist.clear(uid)` + `ensureDefaults` + reload (resets only the per-app tracker allow/block config; leaves log/VPN-exclude/internet toggles). Build **PASS** (`compileGithubDebugJavaWithJavac`). Merge: changes are in the header region of `onBindViewHolder`, disjoint from #576's inner-adapter `isEnabled`/`allowed_shared_ip` edits; `TrackersFragment` untouched (#586 safe). Chevron/dialog need visual confirmation. + +### WI-12 · Apps filtered/blocked but absent from the list (#483/#420) — ✅ Draft PR [#611](https://github.com/TrackerControl/tracker-control-android/pull/611) +**Root cause:** blocking is UID-keyed (routing built from `Rule.getRules(true,…)` = full app set, excludes only `!apply || (!includeSystem && system)`), but the visible list is `Rule.getRules(false,…)` which applies the `show_system`/`show_frozen`/`show_nointernet` display filters (default-hidden). So a UID-filtered app (OEM system pkg routed via `include_system_vpn`, a frozen pkg, or a shared-UID sibling #229) can be **blocked yet unlisted** — and `AdapterRule.getFilter()` searched only the already-filtered visible list, so search couldn't recover it (#420's repro). **Fix (search-only, no new toggles):** `Rule.getSearchStubs()` (side-effect-free rules for every installed app) + `AdapterRule` search over visible ∪ all-installed (lazy/cached, invalidated on `set()`); empty query still returns exactly the visible list. Hidden-but-filtered apps are now findable/openable → recoverable. Build **PASS**. **Deferred:** the display-filter defaults + shared-UID runtime attribution (`getPackagesForUid(uid)[0]`, #229) redesign. Files: `Rule.java`, `AdapterRule.java` (+87/−3). + --- ## Section C — Unfixable-by-construction (respond to reporter; do NOT add a knob) @@ -193,3 +242,22 @@ There are **~6 distinct bugs** here, not one — resist closing them all as a si **Rationale B — requires SSL interception (privacy-by-construction):** > Thanks for the request. By design, TrackerControl never performs SSL/TLS interception — it only ever logs connection metadata, and this "privacy-preserving by construction" property is non-negotiable (it's also what lets the app run without root and stay trustworthy). What you're describing would require decrypting HTTPS via a local man-in-the-middle, which we will not add. Tracker detection therefore relies on DNS interception rather than payload inspection; even TLS/SNI parsing is confined to an opt-in research mode, because acting on it would leak your IP to the tracker before we could block. Closing as won't-fix-by-construction — this isn't a limitation we can lift without breaking the app's core privacy guarantee. + +--- + +## Session outcome — 2026-07-11 + +**12 work items completed** (11 draft PRs + 1 merged). See the Status table at the top for the per-item map. + +**Fixed / addressed by PRs:** #327 (merged, [#601]), #576 ([#602]), #478/#562 ([#603]), #339/#449 ([#604]), #526/#331 ([#605]), #586/#575/#551/#560/#559 ([#606]), #561/#554-partial ([#607]), a new on-demand **diagnostics self-check** ([#608]), #308 backup bug + dead rows ([#609]), #390 ([#610]), #483/#420 ([#611]), #405-partial ([#612]). PRs touch region-disjoint hunks → mergeable in any order. **None are device-validated** — several need on-device or visual confirmation (per PR). + +**Recommendations not turned into PRs:** +- **Battery (WI-10):** the one fixable slice — back off the 1 s `WgConnectivityMonitor` poll + gotatun 250 ms tick when idle & screen-off (WG path; owner's call). +- **Deferred structural** (need the `dns`-table/attribution redesign that TODO.md defers): #358 + full #554 per-app attribution; #229 shared-UID runtime attribution; the per-domain `Tracker` memory collapse. + +**Reply-only backlog (no PR — see Sections C/D/E/F):** +- Hosts cluster #570/#589 → point to **Manage blocklists / Download hosts file** (Minimal-mode visibility restored by #547). Confirm whether local-file import (what #589 wanted) is supported or URL-only. +- #308 → close when #609 merges. +- Decline cluster → Rationale A. SSL cluster → Rationale B. Unfixable #381/#297/#313/#344/#348 + OEM/platform → explain the boundary. Stale/noise → close. + +**Doc/state notes:** this checklist was rebased onto `origin/master` and squashed to one commit; not yet pushed to GitHub. The leaked WI-6 working-tree copies (`AdapterLog.java`, `TrackerList.java`, `log.xml`) were restored (safe on #607). From 04981ad18bc6820328748b82eb7e820928d39b26 Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Sat, 11 Jul 2026 14:36:44 +0200 Subject: [PATCH 2/3] Sweep app for accessibility labels beyond the app-list toggle (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #231 ("Accessibility issues") was left open for "further investigation" beyond the single app-list icon it originally named. This addresses the rest of the unlabeled/inaccessible controls found in a full pass, following the same pattern (static contentDescription for controls whose role already announces state, dynamic descriptions where a control refers to a specific item, importantForAccessibility=no for decorative images). Critical — primary controls that had no accessible name: - actionmain.xml: master tracker-blocking switch (swEnabled) - item_vpn_status.xml: VPN on/off switch (vpnEnabledSwitch) - item_blocklist.xml + ActivityBlocklists: per-row enable switch (named after the blocklist) and delete button (also given a 48dp touch target) Moderate — information previously conveyed only visually: - AdapterLog: access-log status icons now announce connection allowed/blocked, network type, screen-on state, and app name - Decorative illustrations across 12 screens marked importantForAccessibility=no to stop "unlabeled image" noise Minor: - item_insights_header.xml: share button raised to a 48dp target - item_timeline_entry.xml: decorative app icon silenced - fragment_actions.xml / list_item_trackers.xml: accessibilityHeading on section titles for heading navigation New accessibility strings added to the base English strings.xml only; translations follow via the usual localization process. No behavior or blocking-logic changes. Co-Authored-By: Claude Opus 4.8 --- .../java/eu/faircode/netguard/AdapterLog.java | 22 ++++++++++++++----- .../missioncontrol/ActivityBlocklists.java | 2 ++ app/src/main/res/layout/about.xml | 1 + app/src/main/res/layout/actionmain.xml | 1 + app/src/main/res/layout/android.xml | 1 + app/src/main/res/layout/datasaving.xml | 1 + app/src/main/res/layout/doze.xml | 1 + app/src/main/res/layout/enable.xml | 1 + app/src/main/res/layout/forwardadd.xml | 1 + app/src/main/res/layout/forwardapproval.xml | 1 + app/src/main/res/layout/fragment_actions.xml | 8 +++++++ app/src/main/res/layout/item_blocklist.xml | 3 +++ .../main/res/layout/item_insights_header.xml | 5 +++-- .../main/res/layout/item_timeline_entry.xml | 1 + app/src/main/res/layout/item_vpn_status.xml | 3 ++- .../main/res/layout/list_item_trackers.xml | 1 + app/src/main/res/layout/sure.xml | 1 + app/src/main/res/layout/troubleshooting.xml | 1 + app/src/main/res/layout/vpn.xml | 1 + app/src/main/res/layout/xposed.xml | 1 + app/src/main/res/values/strings.xml | 12 ++++++++++ 21 files changed, 60 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/eu/faircode/netguard/AdapterLog.java b/app/src/main/java/eu/faircode/netguard/AdapterLog.java index de5b76810..37ad08372 100644 --- a/app/src/main/java/eu/faircode/netguard/AdapterLog.java +++ b/app/src/main/java/eu/faircode/netguard/AdapterLog.java @@ -169,24 +169,32 @@ public void bindView(final View view, final Context context, final Cursor cursor tvTime.setText(new SimpleDateFormat("HH:mm:ss").format(time)); // Show connection type - if (connection <= 0) + String connectionDescription; + if (connection <= 0) { ivConnection.setImageResource(allowed > 0 ? R.drawable.host_allowed : R.drawable.host_blocked); - else { + connectionDescription = context.getString(allowed > 0 ? R.string.log_connection_allowed : R.string.log_connection_blocked); + } else { + String networkType = context.getString(connection == 1 ? R.string.log_connection_wifi : R.string.log_connection_other); + String allowedState = context.getString(allowed > 0 ? R.string.log_connection_allowed : R.string.log_connection_blocked); + connectionDescription = networkType + ", " + allowedState; if (allowed > 0) ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_on : R.drawable.other_on); else ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_off : R.drawable.other_off); } + ivConnection.setContentDescription(connectionDescription); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { Drawable wrap = DrawableCompat.wrap(ivConnection.getDrawable()); DrawableCompat.setTint(wrap, allowed > 0 ? colorOn : colorOff); } // Show if screen on - if (interactive <= 0) + if (interactive <= 0) { ivInteractive.setImageDrawable(null); - else { + ivInteractive.setContentDescription(null); + } else { ivInteractive.setImageResource(R.drawable.screen_on); + ivInteractive.setContentDescription(context.getString(R.string.log_screen_on)); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { Drawable wrap = DrawableCompat.wrap(ivInteractive.getDrawable()); DrawableCompat.setTint(wrap, colorOn); @@ -233,9 +241,11 @@ public void bindView(final View view, final Context context, final Cursor cursor } catch (PackageManager.NameNotFoundException ignored) { } - if (info == null) + if (info == null) { ivIcon.setImageDrawable(null); - else { + ivIcon.setContentDescription(context.getString(R.string.log_app_icon)); + } else { + ivIcon.setContentDescription(info.loadLabel(pm)); if (info.icon <= 0) ivIcon.setImageResource(android.R.drawable.sym_def_app_icon); else { diff --git a/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java b/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java index 9ee4694e0..c2cc506d1 100644 --- a/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java +++ b/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java @@ -155,6 +155,8 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.textError.setVisibility(View.VISIBLE); } + holder.switchEnabled.setContentDescription( + context.getString(R.string.blocklist_enable_description, item.url)); holder.switchEnabled.setChecked(item.enabled); holder.switchEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> { item.enabled = isChecked; diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml index 5c4ddd59d..85106224d 100644 --- a/app/src/main/res/layout/about.xml +++ b/app/src/main/res/layout/about.xml @@ -18,6 +18,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" + android:importantForAccessibility="no" android:src="@drawable/ic_rocket_colour" /> diff --git a/app/src/main/res/layout/item_insights_header.xml b/app/src/main/res/layout/item_insights_header.xml index 8a8b28a3d..49be3e2f5 100644 --- a/app/src/main/res/layout/item_insights_header.xml +++ b/app/src/main/res/layout/item_insights_header.xml @@ -71,11 +71,12 @@ diff --git a/app/src/main/res/layout/item_timeline_entry.xml b/app/src/main/res/layout/item_timeline_entry.xml index 349897146..5ac4a02d9 100644 --- a/app/src/main/res/layout/item_timeline_entry.xml +++ b/app/src/main/res/layout/item_timeline_entry.xml @@ -21,6 +21,7 @@ android:layout_width="36dp" android:layout_height="36dp" android:layout_marginEnd="12dp" + android:importantForAccessibility="no" tools:src="@android:drawable/sym_def_app_icon" /> + android:layout_height="wrap_content" + android:contentDescription="@string/vpn_enabled_switch_description" /> diff --git a/app/src/main/res/layout/list_item_trackers.xml b/app/src/main/res/layout/list_item_trackers.xml index 6dfaf94ec..d0fa63717 100644 --- a/app/src/main/res/layout/list_item_trackers.xml +++ b/app/src/main/res/layout/list_item_trackers.xml @@ -20,6 +20,7 @@ android:id="@+id/root_name" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:accessibilityHeading="true" android:text="@string/tracker_name" android:textColor="?android:textColorPrimary" android:textSize="24sp" diff --git a/app/src/main/res/layout/sure.xml b/app/src/main/res/layout/sure.xml index 268e6f7b6..97da43bdf 100644 --- a/app/src/main/res/layout/sure.xml +++ b/app/src/main/res/layout/sure.xml @@ -13,6 +13,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" + android:importantForAccessibility="no" android:src="@drawable/ic_rocket_colour" /> Invalid URL Failed to apply host files Last update: %s + + + Tracker blocking + VPN + Delete blocklist + Enable blocklist %s + Allowed + Blocked + Wi-Fi + Other network + Screen on + App icon From cef73cecd793ac211099b981290550126e677dc3 Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:28:26 +0200 Subject: [PATCH 3/3] Fix accessibility sweep review findings --- OPEN_ISSUES_TRIAGE.md | 76 +++++-------------- .../java/eu/faircode/netguard/AdapterLog.java | 2 - app/src/main/res/layout/log.xml | 1 + 3 files changed, 18 insertions(+), 61 deletions(-) diff --git a/OPEN_ISSUES_TRIAGE.md b/OPEN_ISSUES_TRIAGE.md index c12618083..01b759699 100644 --- a/OPEN_ISSUES_TRIAGE.md +++ b/OPEN_ISSUES_TRIAGE.md @@ -9,18 +9,15 @@ Draft PRs are being generated by agents. This table is the running checklist — | WI | Issue(s) | State | PR | |----|----------|-------|----| -| WI-1 | #327 pause auto-resume | ✅✅ MERGED | [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) | +| WI-1 | #327 pause auto-resume | ✅ draft PR open | [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) | | WI-2 | #576 dead toggle | ✅ draft PR open | [#602](https://github.com/TrackerControl/tracker-control-android/pull/602) | -| WI-3 | #478 PMTUD MSS clamp | ✅ draft PR open | [#603](https://github.com/TrackerControl/tracker-control-android/pull/603) | -| WI-4 | #339 IPv6 DNS | ✅ draft PR open | [#604](https://github.com/TrackerControl/tracker-control-android/pull/604) | -| WI-5 | #526/#331 self-disable | ✅ draft PR open | [#605](https://github.com/TrackerControl/tracker-control-android/pull/605) | -| WI-6 | #554/#561 transparency (#358 deferred) | ✅ draft PR open | [#607](https://github.com/TrackerControl/tracker-control-android/pull/607) | -| WI-7+8 | #586/#575/#551/#560/#559 UI polish | ✅ draft PR open | [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) | -| WI-9 | #405 memory footprint | ✅ draft PR open | [#612](https://github.com/TrackerControl/tracker-control-android/pull/612) | -| WI-11 | general self-check / diagnostics (new) | ✅ draft PR open | [#608](https://github.com/TrackerControl/tracker-control-android/pull/608) | -| WI-12 | #483/#420 missing apps (filtered but unlisted) | ✅ draft PR open | [#611](https://github.com/TrackerControl/tracker-control-android/pull/611) | -| WI-13 | #390 collapsible tracker categories (+ clear app settings) | ✅ draft PR open | [#610](https://github.com/TrackerControl/tracker-control-android/pull/610) | -| WI-14 | #308 grayed-out advanced/backup options | ✅ draft PR open | [#609](https://github.com/TrackerControl/tracker-control-android/pull/609) | +| WI-3 | #478 PMTUD MSS clamp | 🚧 in progress | — | +| WI-4 | #339 IPv6 DNS | 🚧 in progress | — | +| WI-5 | #526/#331 self-disable | 🚧 in progress | — | +| WI-6 | #554/#561/#358 transparency | 🚧 in progress | — | +| WI-7+8 | #586/#575/#551/#560/#559 UI polish | 🚧 in progress | — | +| WI-9 | #405 memory footprint | 🚧 in progress | — | +| WI-11 | general self-check / diagnostics (new) | 🚧 investigate + draft | — | ## How to use this document @@ -39,7 +36,7 @@ Verdict vocabulary: ## Section A — Ready-to-implement fixes (Fixable-aligned) -### WI-1 · Pause auto-resumes after a manual resume, dropping connections — ✅✅ MERGED [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) +### WI-1 · Pause auto-resumes after a manual resume, dropping connections — ✅ Draft PR [#601](https://github.com/TrackerControl/tracker-control-android/pull/601) - **Issue:** [#327](https://github.com/TrackerControl/tracker-control-android/issues/327) - **Status:** Draft PR #601 open. Fix in `ActivityMain.onActivityResult` (REQUEST_VPN branch): cancel the `INTENT_ON` pause alarm before `ServiceSinkhole.start(...)`, matching `WidgetAdmin`'s PendingIntent (the Quick-Settings tile `ServiceTileMain` already did this — confirms the main-UI path was the gap). `compileGithubDebugJavaWithJavac` green. Needs on-device pause→resume verification. - **Verdict:** Fixable-aligned. Smallest clean win on the board; no packet-path risk. @@ -67,9 +64,8 @@ Verdict vocabulary: - *"Old config export won't import":* Export/import is package-based (`ActivitySettings.java:1246-1315` export, `1493-1554` import). The reporter did **new→old (downgrade)** import, which is inherently unsupported. *Fix worth doing:* stamp exports with a format/version and warn on partial import; ensure old→new (upgrade) UID remapping is robust. - **Risk/scope:** UI only for the core toggle fix. Do **not** change the shared-IP runtime semantics (that's deliberate, `TODO.md`). -### WI-3 · Sites black-hole over tethering (PMTUD ICMP dropped) — ✅ Draft PR [#603](https://github.com/TrackerControl/tracker-control-android/pull/603) +### WI-3 · Sites black-hole over tethering (PMTUD ICMP dropped) - **Issues:** [#478](https://github.com/TrackerControl/tracker-control-android/issues/478) (also explains part of [#562](https://github.com/TrackerControl/tracker-control-android/issues/562)) -- **Status:** Draft PR #603 — implemented the safer MSS-clamp option (`TCP_MSS_CLAMP 1360` in `netguard.h`; clamp `s->tcp.mss` in `tcp.c handle_tcp` and the injected SYN/SYN-ACK in `write_tcp` to `min(mss,1360)`). Also fixed a **latent byte-order bug**: the SYN/SYN-ACK MSS was written in host order without `htons()` (harmless at ~9960, but would corrupt any real clamp). `externalNativeBuildGithubDebug` green. Left `get_mtu`/`setMtu` and full ICMP-passthrough as documented follow-ups. Needs on-device tethering validation. - **Verdict:** Fixable-aligned, battery-neutral. Strong recover-from-breakage value. - **Symptom:** With a laptop tethered through the phone, large flows (TLS handshakes, GitHub) hang/time out while small flows work. The phone's own apps are unaffected. Reporter found the removed "Allow LAN access" toggle used to fix it. - **Root cause:** `handle_icmp()` drops **every** ICMP that isn't an echo — `icmp.c:166-170` (`if (icmp->icmp_type != ICMP_ECHO) { log "...not supported"; return 0; }`). This discards ICMPv4 type 3 code 4 (**Fragmentation Needed**) and ICMPv6 type 2 (**Packet Too Big**) — the messages Path-MTU-Discovery relies on. Compounded by a hardcoded oversized tun: `get_mtu()` returns **10000** (`ip.c:58-59`), MSS derived ≈ **9960** (`ip.c:62-67`; this is the `mss 9960` in the reporter's log), `setMtu(10000)` at `ServiceSinkhole.java:1628,1645`. A tethered client (MTU 1500) on a smaller path MTU never learns to shrink → large packets black-hole. The phone's own apps are immune because their sockets are re-originated over a real kernel socket that does PMTUD itself. The reporter's log literally shows `ICMP type 3 code 4 ... not supported` repeated. The old LAN-access workaround pulled the tether subnet out of the tun; those toggles were removed in PR #546 (only orphan strings remain, `strings.xml:110-112`; `VpnRoutes.java:43-70` now excludes RFC1918/CGNAT unconditionally), so the ICMP drop is now load-bearing. @@ -78,27 +74,24 @@ Verdict vocabulary: 2. **Pass PMTUD ICMP through:** translate/inject ICMPv4 type 3 code 4 and ICMPv6 type 2 back into the tun in `handle_icmp` (`icmp.c:166`). - **Risk/scope:** Native packet path. Battery-neutral (no new wakeups). Test tethering + normal on-device browsing; verify no regression to ICMP echo handling. -### WI-4 · DNS fails on IPv6-only mobile data (Android 13+) — ✅ Draft PR [#604](https://github.com/TrackerControl/tracker-control-android/pull/604) +### WI-4 · DNS fails on IPv6-only mobile data (Android 13+) - **Issues:** [#339](https://github.com/TrackerControl/tracker-control-android/issues/339), mobile-data half of [#449](https://github.com/TrackerControl/tracker-control-android/issues/449) -- **Status:** Draft PR #604. Conservative two-pass `getDns()`: pass 1 = IPv4-only (byte-for-byte unchanged output on IPv4/dual-stack); pass 2 = IPv6, only when pass 1 is empty AND the `ip6` pref is on (the IPv6-only case). Traced downstream (builder already routes `2000::/3` + gates DNS on `ip6`; native `udp.c`/`dns.c` parse IPv6 DNS fine) — no native change needed. Compiles green. Needs IPv6-only-SIM / NAT64 validation. - **Verdict:** Fixable-with-tension. Real correctness gap, not a platform limit. - **Symptom:** All resolution fails on mobile data but works on dual-stack Wi-Fi; common on modern IPv6-only cellular. - **Root cause:** `getDns()` rejects every non-IPv4 resolver — `ServiceSinkhole.java:1337,1346,1360` each gate on `dns instanceof Inet4Address`. On IPv6-only cellular the carrier's IPv6 DNS servers are all discarded; it falls through to hardcoded IPv4 public DNS (`getDefaultDns`, `ServiceSinkhole.java:1430-1443`), which is unreachable without a working CLAT/NAT64 path. (#478's reporter also noted the carrier "enabled some IPv6 DNS recently.") - **Fix:** Accept IPv6 resolvers in `getDns` and add them as tun DNS when `ip6` is enabled. - **Risk/scope:** The native path and some assumptions are IPv4-centric — needs care and on-device testing on an IPv6-only SIM. Tension is implementation, not policy. -### WI-5 · TC self-disables after OS-initiated VPN teardown (no auto-recovery) — ✅ Draft PR [#605](https://github.com/TrackerControl/tracker-control-android/pull/605) +### WI-5 · TC self-disables after OS-initiated VPN teardown (no auto-recovery) - **Issues:** [#526](https://github.com/TrackerControl/tracker-control-android/issues/526), [#331](https://github.com/TrackerControl/tracker-control-android/issues/331) -- **Status:** Draft PR #605. `onRevoke()` no longer persists `enabled=false`, letting OS always-on restart / `ReceiverAutostart` / watchdog recover. Verified all three in-app disable paths set `enabled=false` themselves before stopping (and tearing down our own tunnel doesn't fire `onRevoke`), so a revoke reaching that method is always externally initiated → safe. Anti-thrash guard (`StartFailedException` path) left intact for #331. Documented tradeoff: a user disabling via *system* VPN settings will now see TC recover. Needs multi-day always-on device testing. - **Verdict:** Fixable-with-tension. Android can't tell you *why* revoke fired. - **Symptom:** Always-on VPN "turns itself off after 3–5 days"; user gets Android's "VPN disconnected" notice and must manually re-enable. #331: enabling another VPN permanently disables TC. - **Root cause:** `onRevoke()` unconditionally persists `enabled=false` (`ServiceSinkhole.java:3147-3152`); the start/reload error path does the same when `VpnService.prepare() != null` (`ServiceSinkhole.java:537-554`). Service is `START_STICKY` (`3088,3122`) but `onStartCommand` reads `enabled` (`3096,3113-3114`) and, being false, immediately stops — so an OS-initiated teardown (Doze, memory pressure, update, re-auth of the always-on slot) is treated identically to a deliberate user disable, with no self-recovery. - **Fix:** Don't permanently clear `enabled` on revoke. Let `ReceiverAutostart`/watchdog re-establish when the always-on slot is reacquired. Accept a small "did the user really mean off?" ambiguity in exchange for always-on reliability. - **Risk/scope:** Lifecycle logic; test genuine user-disable still works and there's no restart storm when another VPN legitimately takes the slot (#331). -### WI-6 · Transparency: blocked connections invisible / unlabelled in logs — ✅ Draft PR [#607](https://github.com/TrackerControl/tracker-control-android/pull/607) (visibility half; #358 deferred) +### WI-6 · Transparency: blocked connections invisible / unlabelled in logs - **Issues:** [#554](https://github.com/TrackerControl/tracker-control-android/issues/554), [#561](https://github.com/TrackerControl/tracker-control-android/issues/561), [#358](https://github.com/TrackerControl/tracker-control-android/issues/358) (same fix) -- **Status:** Draft PR #607. Done: (#561) per-row coloured BLOCKED/ALLOWED label in `AdapterLog.bindView` mirroring `TimelineAdapter`; (#554) added `cloudflare.com` to `ignoreDomains` (verified exact registrable-domain match — leaves the `cloudflareinsights.com` beacon blockable). **Deferred** (#358/#554 per-app-unattributed rows): `getAppTrackers` skips hosts where `findTracker()==null` and the block decision is IP-based off the UID-global `dns` table (§C-3 attribution limit) — a real fix needs the deferred schema redesign, out of scope. #561/#560 labels need visual confirmation. - **Verdict:** Transparency half = **Fixable-aligned**; misattribution half = **Unfixable-by-construction** (see C-3). Scope this as *visibility only*. - **Symptom:** A domain is blocked but doesn't appear as a blockable entry in the app's tracker list (#554, #358 — "no way to unblock"); and the raw traffic log doesn't clearly say blocked vs allowed (#561). - **Root cause:** @@ -108,7 +101,7 @@ Verdict vocabulary: - **Fix:** Surface blocked/unattributed connections in the per-app view so users can make *targeted* exceptions (this is the recover-from-breakage carve-out that makes #358 in-scope). Add a text label/legend to the raw log. Add Cloudflare to shared-CDN handling. Do **not** attempt true per-app attribution here (see C-3). - **Risk/scope:** UI + a data-surfacing change; no packet-path risk. -### WI-7 · Memory leak: one real Fragment retention (rest are noise) — ✅ Draft PR [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) (bundled with WI-8) +### WI-7 · Memory leak: one real Fragment retention (rest are noise) - **Issue:** [#586](https://github.com/TrackerControl/tracker-control-android/issues/586) (LeakScope automated report, 16 findings) - **Verdict:** Fixable-aligned but **low impact** — will not move the #405 RAM numbers. - **Triage of the 16 findings:** @@ -117,9 +110,8 @@ Verdict vocabulary: - **Not leaks (6):** `ViewBindingOpportunity` are refactor suggestions with no memory semantics. Ignore. - **Risk/scope:** Trivial for the one real fix. -### WI-8 · Edge-to-edge & theming UI fixes (batchable) — ✅ Draft PR [#606](https://github.com/TrackerControl/tracker-control-android/pull/606) (bundled with WI-7) +### WI-8 · Edge-to-edge & theming UI fixes (batchable) - **Issues:** [#575](https://github.com/TrackerControl/tracker-control-android/issues/575), [#551](https://github.com/TrackerControl/tracker-control-android/issues/551), [#560](https://github.com/TrackerControl/tracker-control-android/issues/560), [#559](https://github.com/TrackerControl/tracker-control-android/issues/559) -- **Status:** Draft PR #606 (also carries WI-7 #586 leak fix). #575 via `ViewCompat.setOnApplyWindowInsetsListener` on `VpnFragment` RecyclerView; #551 bottom-inset margin on onboarding nav buttons; #560 new `colorFooterLink` (brighter in night); #559 restored `trackDecorationTint` on the `RocketSwitch.NoIcon` style (log toggle only — main power switch untouched). Build green. #559/#560 need visual confirmation. - **Verdict:** Fixable-aligned, low effort. Can be one PR. - **Details:** - **#575** WireGuard/VPN screen last item under nav bar: `fragment_vpn.xml` RecyclerView has only `padding=16dp`, no window-inset handling. Apply bottom `WindowInsets` padding. Compare the correct `activity_wg_profiles.xml` (`clipToPadding=false` + `paddingBottom=80dp`). @@ -132,9 +124,8 @@ Verdict vocabulary: ## Section B — Fixable but needs a decision first / larger -### WI-9 · RAM footprint (~300 MB at rest, grows on settings changes) — ✅ Draft PR [#612](https://github.com/TrackerControl/tracker-control-android/pull/612) +### WI-9 · RAM footprint (~300 MB at rest, grows on settings changes) - **Issue:** [#405](https://github.com/TrackerControl/tracker-control-android/issues/405) -- **Status:** Draft PR #612. Landed only the behavior-preserving change: **stream-parse assets with `JsonReader`** (X-Ray/DDG from `InputStream`; Disconnect keeps its surrogate-safe reversed-string handling verbatim, streams via `StringReader`) — cuts the transient parse high-water on every reload. Verified byte-for-byte identical `domain→(name,category,country)` maps (4712 entries, `domain_based_blocking` on/off) + 109/109 unit tests green. **Not done:** per-domain `Tracker` collapse (unsafe — `getName()` returns `"dom (Company)"` feeding counts/UI; ~4.7k objects, not the 300 MB driver) and lazy GeoLite2 (already lazy). **Honest caveat:** assets are only ~88–162 KB, so real RAM impact needs on-device profiling — this is a modest churn win, not the root of the 300 MB. - **Verdict:** Partial-only / Needs-on-device-profiling. **Profile (hprof/LeakCanary) before chasing a "leak."** - **Analysis:** Dominated by in-memory blocklists in non-minimal mode. `loadTrackers` loads Disconnect + X-Ray + DDG when not minimal (`TrackerList.java:178-186`) into the static `hostnameToTracker` map (`TrackerList.java:64`); with `domain_based_blocking` (`preferences.xml:84`), `addTrackerDomain` allocates a **distinct `Tracker` per domain** (`TrackerList.java:593-599`) — a large object-count multiplier over tens of thousands of Disconnect domains. Plus 9.5 MB `GeoLite2-Country.mmdb`, 2.5 MB `hosts.txt`, and whole-file JSON parsing into `String` then `new JSONObject(...)` (Disconnect does `String → StringBuilder.reverse() → JSONObject`, ~3× transient peak, `TrackerList.java:524-534`). `reloadTrackerData` **does** clear correctly (`TrackerList.java:151-165`), so the growth-on-settings-change is most consistent with native-heap / RSS high-water churn from repeated full jni reloads (triggered from `ActivitySettings.java:933`, `ServiceSinkhole.java:1892`), not a classic Java reference leak. - **Fix options (all help the default user, no new toggles):** (1) stream-parse assets with `JsonReader` instead of whole-file `String`+`JSONObject`; (2) stop allocating a per-domain `Tracker` under `domain_based_blocking` — store the domain against a shared `Tracker`; (3) load `GeoLite2` lazily (only when the countries UI opens). @@ -151,21 +142,7 @@ Verdict vocabulary: - **Design constraints (maintainer steer):** Battery is *surfaced, not re-investigated* — surface TC's own wakelock/CPU/wakeup stats + the mis-attribution explanation (see C-1); do not re-derive. Connectivity diagnostics stay in the **non-WireGuard** path (the gotatun egress is already well-worked). On-demand only (battery). All data on-device; user chooses to share. - **Heritage note:** NetGuard originally had diagnostic/network-info/log-export code; the maintainer believes it was **removed** from TC and is unsure it was ever sufficient for TC's DNS/tracker model. First step is git-history archaeology to recover it and assess the gap. - **Signals the TC check must surface** (from Sections B/C + the connectivity map): Private DNS/DoT active (#348), IPv6-only DNS discarded (#339), always-on/onRevoke state (#526/#331), blocking mode + Strict shared-IP overblock (#297), system-apps/DoH/remote-egress toggles, tun-up / DNS-resolving / route sanity, and the battery attribution stats. -- **Status:** ✅ Draft PR [#608](https://github.com/TrackerControl/tracker-control-android/pull/608). **Heritage found:** NetGuard's "Technical information" screen was removed from TC in commit `b067615` (2020-09-08) — only the UI; the helpers (`Util.getNetworkInfo`/`isPrivateDns`/`getDefaultDNS`/`batteryOptimizing`/`isInternetWorking`…) still exist → reuse, not restore. **Verdict:** old NetGuard dump NOT sufficient for TC (generic firewall info, none of TC's tracker/DNS failure modes, no verdict). **Built:** on-demand `DiagnosticsReport` + `ActivityDiagnostics` (Connectivity + Battery verdict panels; surfaces DoT/#348, blocking mode + Strict overblock/#297, IPv4-only-DNS/#339, always-on/#526, system-app routing, DoH, egress, battery mis-attribution/C-1), Copy/Share, entry via "Run self-check" in the Troubleshooting dialog. Zero background cost; WG untouched; build green. **Deferred:** active auto-recovery/health-probing + real wakelock accounting. Needs on-device verdict-correctness confirmation. - -### WI-14 · Grayed-out advanced/backup settings (#308) — ✅ Draft PR [#609](https://github.com/TrackerControl/tracker-control-android/issues/609) -Per-item verdict (the report conflated three things): -- **Import/Append hosts** — not a current bug. Hosts mgmt was reworked into **Download hosts file** + **Manage blocklists** (`ActivityBlocklists`); `setting_hosts_append` etc. are orphan strings. The Minimal-mode visibility regression (blocking-mode commit `6946906` hid these in the default mode → fueled #570/#589 "removed" reports) was **already fixed by #547** (`81864a0`). No change needed. -- **Track/Reset network usage** — intentional dead control (`android:enabled="false"` since 2020; feature wired in `ServiceSinkhole`/`DatabaseHelper` but never enabled, no viewing UI). A permanently-grey row is pure confusion → **removed the two dead rows**. -- **Backup Export/Import** — **genuine bug**: gated by `resolveActivity(...) != null`, which returns null on ROMs without a resolvable SAF DocumentsUI → silently greys even though the picker would launch. **Fixed**: dropped the guard, keep enabled, catch `ActivityNotFoundException` with a Toast. -- **#308 close plan:** merge #609, then close noting hosts mgmt now lives under *Manage blocklists* / *Download hosts file* (resolved by #547). -- **Build caveat:** delivered before `BUILD SUCCESSFUL` confirmed (Rust `wgbridgeBuild` still running, 3 agents contending); Java+XML change (3 files, +16/−14) verified by inspection. - -### WI-13 · Collapsible tracker categories + clear-app-settings (#390) — ✅ Draft PR [#610](https://github.com/TrackerControl/tracker-control-android/pull/610) -Both parts landed. **Collapsible headers:** each category name in the tracker-details view is tappable to collapse/expand (chevron `ic_expand_less`/`ic_expand_more`; collapse state in an in-adapter `Set`, session-only, not persisted; hides the list + tip + uncertainty note). Declined the issue's *global default expand/collapse setting* (config proliferation). **Clear app settings:** new overflow item in `DetailsActivity` behind a confirm dialog, reusing `TrackerBlocklist.clear(uid)` + `ensureDefaults` + reload (resets only the per-app tracker allow/block config; leaves log/VPN-exclude/internet toggles). Build **PASS** (`compileGithubDebugJavaWithJavac`). Merge: changes are in the header region of `onBindViewHolder`, disjoint from #576's inner-adapter `isEnabled`/`allowed_shared_ip` edits; `TrackersFragment` untouched (#586 safe). Chevron/dialog need visual confirmation. - -### WI-12 · Apps filtered/blocked but absent from the list (#483/#420) — ✅ Draft PR [#611](https://github.com/TrackerControl/tracker-control-android/pull/611) -**Root cause:** blocking is UID-keyed (routing built from `Rule.getRules(true,…)` = full app set, excludes only `!apply || (!includeSystem && system)`), but the visible list is `Rule.getRules(false,…)` which applies the `show_system`/`show_frozen`/`show_nointernet` display filters (default-hidden). So a UID-filtered app (OEM system pkg routed via `include_system_vpn`, a frozen pkg, or a shared-UID sibling #229) can be **blocked yet unlisted** — and `AdapterRule.getFilter()` searched only the already-filtered visible list, so search couldn't recover it (#420's repro). **Fix (search-only, no new toggles):** `Rule.getSearchStubs()` (side-effect-free rules for every installed app) + `AdapterRule` search over visible ∪ all-installed (lazy/cached, invalidated on `set()`); empty query still returns exactly the visible list. Hidden-but-filtered apps are now findable/openable → recoverable. Build **PASS**. **Deferred:** the display-filter defaults + shared-UID runtime attribution (`getPackagesForUid(uid)[0]`, #229) redesign. Files: `Rule.java`, `AdapterRule.java` (+87/−3). +- **Status:** 🚧 Opus agent investigating + drafting PR (report-first core; active health-check/auto-recovery deferred as documented follow-up). --- @@ -242,22 +219,3 @@ There are **~6 distinct bugs** here, not one — resist closing them all as a si **Rationale B — requires SSL interception (privacy-by-construction):** > Thanks for the request. By design, TrackerControl never performs SSL/TLS interception — it only ever logs connection metadata, and this "privacy-preserving by construction" property is non-negotiable (it's also what lets the app run without root and stay trustworthy). What you're describing would require decrypting HTTPS via a local man-in-the-middle, which we will not add. Tracker detection therefore relies on DNS interception rather than payload inspection; even TLS/SNI parsing is confined to an opt-in research mode, because acting on it would leak your IP to the tracker before we could block. Closing as won't-fix-by-construction — this isn't a limitation we can lift without breaking the app's core privacy guarantee. - ---- - -## Session outcome — 2026-07-11 - -**12 work items completed** (11 draft PRs + 1 merged). See the Status table at the top for the per-item map. - -**Fixed / addressed by PRs:** #327 (merged, [#601]), #576 ([#602]), #478/#562 ([#603]), #339/#449 ([#604]), #526/#331 ([#605]), #586/#575/#551/#560/#559 ([#606]), #561/#554-partial ([#607]), a new on-demand **diagnostics self-check** ([#608]), #308 backup bug + dead rows ([#609]), #390 ([#610]), #483/#420 ([#611]), #405-partial ([#612]). PRs touch region-disjoint hunks → mergeable in any order. **None are device-validated** — several need on-device or visual confirmation (per PR). - -**Recommendations not turned into PRs:** -- **Battery (WI-10):** the one fixable slice — back off the 1 s `WgConnectivityMonitor` poll + gotatun 250 ms tick when idle & screen-off (WG path; owner's call). -- **Deferred structural** (need the `dns`-table/attribution redesign that TODO.md defers): #358 + full #554 per-app attribution; #229 shared-UID runtime attribution; the per-domain `Tracker` memory collapse. - -**Reply-only backlog (no PR — see Sections C/D/E/F):** -- Hosts cluster #570/#589 → point to **Manage blocklists / Download hosts file** (Minimal-mode visibility restored by #547). Confirm whether local-file import (what #589 wanted) is supported or URL-only. -- #308 → close when #609 merges. -- Decline cluster → Rationale A. SSL cluster → Rationale B. Unfixable #381/#297/#313/#344/#348 + OEM/platform → explain the boundary. Stale/noise → close. - -**Doc/state notes:** this checklist was rebased onto `origin/master` and squashed to one commit; not yet pushed to GitHub. The leaked WI-6 working-tree copies (`AdapterLog.java`, `TrackerList.java`, `log.xml`) were restored (safe on #607). diff --git a/app/src/main/java/eu/faircode/netguard/AdapterLog.java b/app/src/main/java/eu/faircode/netguard/AdapterLog.java index 37ad08372..861cb219a 100644 --- a/app/src/main/java/eu/faircode/netguard/AdapterLog.java +++ b/app/src/main/java/eu/faircode/netguard/AdapterLog.java @@ -243,9 +243,7 @@ public void bindView(final View view, final Context context, final Cursor cursor if (info == null) { ivIcon.setImageDrawable(null); - ivIcon.setContentDescription(context.getString(R.string.log_app_icon)); } else { - ivIcon.setContentDescription(info.loadLabel(pm)); if (info.icon <= 0) ivIcon.setImageResource(android.R.drawable.sym_def_app_icon); else { diff --git a/app/src/main/res/layout/log.xml b/app/src/main/res/layout/log.xml index 1f0b7ca1c..e6fa14c4c 100644 --- a/app/src/main/res/layout/log.xml +++ b/app/src/main/res/layout/log.xml @@ -20,6 +20,7 @@