Skip to the content.

Background-location proof — collection & egress with the app not open (TT Festival, Dutch GP, Museumnacht)

Phase 4 of the comparison. Phase 3 (03-ondevice-egress-dutchgp-museumnacht.md, merged in #12) proved a live on-device collect+upload to CrowdConnected’s AWS Firehose. The open question it left was the one a user actually cares about: does this happen while the app is not open? This document answers it — for all three apps, each placed in its own geozone, with the screen off and the app never foregrounded during the watched window.

Method: on-device signals only — no proxy, no CA, no MITM, no packet capture. Mock GPS (Android cmd location test providers) placed the device at each app’s geozone centre; the screen was put to sleep (input keyevent KEYCODE_SLEEP → mWakefulness=Dozing) so no app was on screen. Evidence = (a) the apps’ own AWS Kinesis Firehose record-buffer files (…/cache/CrowdConnected/firehose/{positions,observations,logs}/KinesisRecorder/kinesis_firehose_records), (b) logcat upload lines attributed by process PID, and (c) Android’s own OS records — dumpsys appops (per-access uid-state tags) and dumpsys location (standing registrations). Dedicated rooted Pixel 10a, Android 16, 2026-06-27, ~11:41–11:56 CEST. Raw artifacts + SHA-256 manifest are held off-repo in the capture evidence set (evidence/background-location/, 18 artifacts).


0. TL;DR

  1. TT Festival (com.appmiral.ttfestival) collects and uploads a GPS position with the screen off and the app not open. During a 4-minute window with mWakefulness=Dozing and the app never on screen, its positions buffer ran a full collect → upload cycle (0 → 281 → 0 bytes) and its PID (25675) issued two Firehose_20150804.PutRecordBatch → submitAllRecords sent 1 records uploads to firehose.us-east-1.amazonaws.com. Three independent artifacts agree on the same event (on-disk buffer, app logcat, OS AppOps).
  2. Android’s own records make “app not open” provable without any extra tooling. dumpsys appops timestamps every location access and tags it by process state — [top-s] foreground, [fgsvc-s] foreground-service (app off-screen), [bg-s] pure background. TT shows MONITOR_HIGH_POWER_LOCATION [fgsvc-s] 11:44:26 (+2m6s); Dutch GP shows a pure-background FINE_LOCATION [bg-s] 2026-06-27 02:04:54 (2 AM, nobody touching the phone).
  3. TT holds a persistent 30-second high-accuracy background location request. dumpsys location lists Request[@+30s0ms HIGH_ACCURACY … WorkSource{10436 com.appmiral.ttfestival}] (26 live mentions) — the OS-level subscription that drives the background collection.
  4. The two legacy apps stayed silent — because the server gate is off, not because they lack permission. Dutch GP’s buffer sat flat at its stale 261-byte queued record (never flushed); Museumnacht’s stayed 0/0/0. Both have activeAPIResponse=…,false,…. Crucially, all three apps had ACCESS_FINE/COARSE/BACKGROUND_LOCATION = granted=true during this capture (permissions_grant_state.txt), so the negatives are attributable to the server active gate, not a missing OS permission — the controlled isolation of which is Phase 3 §5’s permission-granted re-test (evidence/ondevice-cc/recheck2-permgrant-2026-06-27/).

1. Apps, geozones and the two gates

Each app was mocked to its own geozone centre (the circles differ per app):

App Geozone centre (mock) inner r1 outer r2 Server gate at capture
com.appmiral.ttfestival 52.994439, 6.563694 (Assen / TT) 820 m 60 000 m active (Settings.active=1; no activeAPIResponse; gpsInZoneGenerated=true)
com.appmiral.dutchgp 52.387479, 4.544506 (Circuit Zandvoort) 800 m 34 000 m off (activeAPIResponse=120,false,false,…)
com.appmiral.museumnachtamsterdam 52.363654, 4.89389 (Amsterdam) 10 155 m 80 000 m off (activeAPIResponse=120,false,false,…)

Collection requires two independent gates open; they must not be conflated:

  1. OS location permission — ACCESS_FINE/COARSE_LOCATION and, for background, ACCESS_BACKGROUND_LOCATION must be granted=true. If denied, the app cannot collect regardless of the server flag.
  2. CC server runtime flag — a server-controlled active/inactive state, surfaced as activeAPIResponse=…,false,… in the legacy apps and relocated to a Settings.active column in TT’s newer SDK 2.1.0 (active=1 = on; see Phase 3 §6). If the server marks the SDK inactive it deactivates and collects/uploads nothing even with permission granted (proven in Phase 3 §5).

Verified grant state at capture (permissions_grant_state.txt): all three apps had FINE + COARSE + BACKGROUND location granted=true. TT’s FINE/COARSE carry USER_SET (a real “Allow all the time” tap); Dutch GP and Museumnacht show no USER_SET — granted programmatically (adb pm grant) to open the OS gate so the server gate could be tested in isolation. So gate 1 was open for all three; any negative below is gate 2 (server).


2. TT Festival — POSITIVE: collects and uploads with the screen off

TT runs a persistent CrowdConnectedService foreground service. With the device Dozing and the app never foregrounded (top activity blank at every sample), the positions buffer completed a full cycle and the OS logged a foreground-service location access spanning it.

(a) On-disk queue (logs/bg_timeline.csv, screen Dozing throughout):

11:45:18  positions = 0
11:45:32  positions = 281      ← a fix collected & queued in the background
11:45:45  positions = 281
11:45:58  positions = 0        ← batch flushed = uploaded

(b) App runtime (ttfestival/bg_egress_logcat.txt, all PID 25675) — two upload cycles:

11:44:26  25675  GEO_OBSERVER: Transitioning to high settings
11:44:26  25675  KINESIS : Sending batch of positions via kinesis
11:44:26  25675  AWS4Signer: host:firehose.us-east-1.amazonaws.com
11:44:26  25675  AWS4Signer: x-amz-target:Firehose_20150804.PutRecordBatch
11:44:27  25675  AbstractKinesisRecorder: submitAllRecords sent 1 records
11:45:53  25675  KINESIS : Sending batch of positions via kinesis
11:45:53  25675  AWS4Signer: host:firehose.us-east-1.amazonaws.com   (PutRecordBatch)
11:45:53  25675  AbstractKinesisRecorder: submitAllRecords sent 1 records

(c) OS record (ttfestival/appops_after_bg.txt) — location accessed by a process in foreground-service state ([fgsvc-s] = app not on screen), spanning the upload:

MONITOR_HIGH_POWER_LOCATION  Access: [fgsvc-s] 2026-06-27 11:44:26 duration=+2m6s862ms
FINE_LOCATION                Access: [fgsvc-s] 2026-06-27 11:46:34
----Location, Microphone, and Camera App Ops (Last 1 Hour)----
10436|com.appmiral.ttfestival|…|FINE_LOCATION|…|2026-06-27 11:46:33…|fgsvc|s|…

(d) Standing registration (logs/dumpsys_location_after.txt):

+registration … fused_location_provider …
  Request[@+30s0ms HIGH_ACCURACY, minUpdateInterval=0, … WorkSource{10436 com.appmiral.ttfestival}]

→ TT collected and shipped a position to AWS Firehose with the screen off and the app not open — independently corroborated by the app’s own on-disk queue, its AWS-SDK logcat, and the OS’s AppOps log, all at matching timestamps. This is the background analogue of the festival-night capture and Phase 3’s live cycle.

3. Dutch GP — NEGATIVE while server-gated (configured for background, suppressed)

Backgrounded (Dozing) and mocked to its zone centre for 3 minutes: the positions buffer stayed flat at 261 bytes — the stale Phase-3 record still queued and never flushed, because activeAPIResponse=…,false,… keeps the SDK deactivated (it won’t even drain its backlog). No submitAllRecords/PutRecordBatch from its PID.

But the OS record proves it is wired for pure-background location (dutchgp/appops_after_bg.txt):

FINE_LOCATION  Access: [bg-s]  2026-06-27 02:04:54   ← background access overnight, app not open
FINE_LOCATION  Access: [top-s] 2026-06-27 11:47:29   ← foreground, from our launch

dumpsys location attributed 6 mentions to WorkSource{… com.appmiral.dutchgp}. → Configured for background collection (and has accessed location in the background), but transmitted nothing during this window because the server gate is off.

4. Museumnacht — NEGATIVE / dormant

Backgrounded (Dozing), mocked to centre for 3 minutes: all buffers flat at 0. Only a foreground ([top-s] 11:51:43) access from our own launch; no background access, and no standing dumpsys location registration (0 mentions). Wired + consented but dormant — same server gate, and the SDK never armed a background request.

Permission-confound — resolved by the Phase 3 re-test. An earlier on-device re-check had run Museumnacht with its OS location permission off (a confound: with location denied the app can’t collect regardless of the server gate). In this background run MN’s permission was functional (AppOps records the [top-s] 11:51:43 access; permissions_grant_state.txt shows FINE+COARSE+BACKGROUND granted=true). The decisive controlled isolation is Phase 3 §5 (03-ondevice-egress-dutchgp-museumnacht.md, evidence/ondevice-cc/recheck2-permgrant-2026-06-27/): with permission explicitly granted, in-zone, geoEnabled=1/sendPositionData=1, registered + consented, MN still collected nothing for 210 s while activeAPIResponse=…,false,… (its only egress was AWS SNS push, sns.eu-west-1, not the location Firehose). MN’s negative here is therefore server-suppression, not a missing permission.

5. Summary

App App open? Screen Buffer Upload (PutRecordBatch) OS background access Verdict
TT Festival No Off (Dozing) 0 → 281 → 0 Yes ×2 (11:44, 11:45) [fgsvc-s] 11:44–11:46 Collects + uploads in background
Dutch GP No Off (Dozing) flat 261 (stale, never flushed) No [bg-s] 02:04 (overnight) Configured, server-suppressed
Museumnacht No Off (Dozing) flat 0 No none (foreground only) Wired, dormant

How to see “all location access by app” (the methodology answer). Android records every location access with no extra tooling: dumpsys appops --package <pkg> (per-op Access: lines tagged [top-s]/[fgsvc-s]/[bg-s] + a “Last 1 Hour” Location/Mic/Camera table), dumpsys location (live +registration requests with cadence, accuracy and owning WorkSource{uid pkg}), and the on-device Settings → Privacy → Location access dashboard. Per-call corroboration (optional) via LSPosed+XPrivacyLua or a Frida hook on LocationManager — not required here, as the OS records + on-disk egress already converge.

6. Limitations & disclosures

7. Evidence set (off-repo, hashed)

Held in the capture evidence directory evidence/background-location/ with a SHA-256 MANIFEST.sha256 (18 artifacts):