diff --git a/OPEN_ISSUES_TRIAGE.md b/OPEN_ISSUES_TRIAGE.md index 3603e9df8..01b759699 100644 --- a/OPEN_ISSUES_TRIAGE.md +++ b/OPEN_ISSUES_TRIAGE.md @@ -3,6 +3,22 @@ **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 | ✅ 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 | 🚧 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 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 +36,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 — ✅ 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. - **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: @@ -118,6 +136,14 @@ 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:** 🚧 Opus agent investigating + drafting PR (report-first core; active health-check/auto-recovery deferred as documented follow-up). + --- ## Section C — Unfixable-by-construction (respond to reporter; do NOT add a knob) diff --git a/app/src/main/java/eu/faircode/netguard/AdapterLog.java b/app/src/main/java/eu/faircode/netguard/AdapterLog.java index de5b76810..861cb219a 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,9 @@ 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 { + } else { 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/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 @@ 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