feat(debug): dev-only pause/halt mail-fetch hook for test harnesses (#393)

The on-device perf harness cannot force a genuinely uncached body fetch: proactive
backfill (#12) and post-sync body prefetch (#88/#89) warm the cache before a test can
open a message. Add a debug-only, adb-reachable hook to pause proactive fetch so a real
uncached open can be measured.

Components:
- DebugFetchGate (src/main): thread-safe in-memory holder of paused FetchScopes
  (BACKFILL, PREFETCH; `all` alias). Defaults to not-paused; HEADER_SYNC and on-demand
  OPEN are never gateable.
- FetchGateReceiver (src/debug only): BroadcastReceiver registered in the debug manifest,
  driven by `adb shell am broadcast -a org.libremail.debug.FETCH_GATE -n .../FetchGateReceiver
  --es action <pause|resume|query> --es scope <backfill,prefetch|all>`. Returns the state as
  ordered-broadcast result data (paused=[...]) for a synchronous read-back.

Enforcement (each read guarded by BuildConfig.DEBUG so R8 strips it from release):
- BackfillWorker.doWork() entry -> skip-and-reschedule when BACKFILL is paused, mirroring
  the existing cache-lock deferral (covers periodic + backfillNow()).
- MailSyncer/MailBackfiller.prefetchIfEnabled -> early-return when PREFETCH is paused.
  openMessage / fetchBodyMarkingSeen / fetchAttachment are deliberately NOT gated.

Debug-only: receiver + <receiver> live wholly in src/debug; every gate read in main is
behind BuildConfig.DEBUG. Verified on assembleRelease that R8 strips DebugFetchGate /
FetchScope / FetchGateReceiver and the log strings from the release APK, and the merged
release manifest has no FETCH_GATE receiver.

PII-free AppLog breadcrumbs on pause/resume/query and on each gate-triggered defer/skip
(scope names only).

Tests: DebugFetchGateTest, BackfillWorkerTest / MailSyncerTest / MailBackfillerTest
enforcement cases, and FetchGateReceiverInstrumentedTest (ordered-broadcast -> gate ->
read-back; gated worker defers while an un-gated path runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 20:13:22 -05:00
co-authored by Claude Opus 4.8
parent cfd433af6f
commit 8f8608a430
12 changed files with 646 additions and 3 deletions
+17 -1
View File
@@ -1,5 +1,6 @@
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--
Debug-only test harness for issue #221 (never merged into a release APK — this manifest belongs to
@@ -16,6 +17,21 @@
android:authorities="${applicationId}.coldopen"
android:exported="false"
android:process=":coldopen" />
<!--
Debug-only fetch-gate receiver (issue #393; also never merged into a release APK — this
manifest belongs to the debug source set). Lets the on-device perf harness pause proactive
fetch (backfill + body prefetch) via `adb shell am broadcast` so a genuine uncached
message-open can be measured. Must be exported="true" so the adb `shell` UID can reach it by
explicit component (`-n`); it targets the debug BuildConfig.DEBUG-guarded DebugFetchGate only,
carries no PII, and — being debug-only — can never ship. tools:ignore suppresses the
exported-without-permission lint note: a signature permission would (by design) also lock out
the shell UID this hook exists to serve.
-->
<receiver
android:name="org.libremail.debug.FetchGateReceiver"
android:exported="true"
tools:ignore="ExportedReceiver" />
</application>
</manifest>