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 locationtest 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)logcatupload lines attributed by process PID, and (c) Android’s own OS records —dumpsys appops(per-access uid-state tags) anddumpsys 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
- 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 withmWakefulness=Dozingand the app never on screen, itspositionsbuffer ran a full collect → upload cycle (0 → 281 → 0 bytes) and its PID (25675) issued twoFirehose_20150804.PutRecordBatch→submitAllRecords sent 1 recordsuploads tofirehose.us-east-1.amazonaws.com. Three independent artifacts agree on the same event (on-disk buffer, applogcat, OS AppOps). - Android’s own records make “app not open” provable without any extra tooling.
dumpsys appopstimestamps every location access and tags it by process state —[top-s]foreground,[fgsvc-s]foreground-service (app off-screen),[bg-s]pure background. TT showsMONITOR_HIGH_POWER_LOCATION [fgsvc-s] 11:44:26 (+2m6s); Dutch GP shows a pure-backgroundFINE_LOCATION [bg-s] 2026-06-27 02:04:54(2 AM, nobody touching the phone). - TT holds a persistent 30-second high-accuracy background location request.
dumpsys locationlistsRequest[@+30s0ms HIGH_ACCURACY … WorkSource{10436 com.appmiral.ttfestival}](26 live mentions) — the OS-level subscription that drives the background collection. - 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 hadACCESS_FINE/COARSE/BACKGROUND_LOCATION = granted=trueduring this capture (permissions_grant_state.txt), so the negatives are attributable to the serveractivegate, 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:
- OS location permission —
ACCESS_FINE/COARSE_LOCATIONand, for background,ACCESS_BACKGROUND_LOCATIONmust begranted=true. If denied, the app cannot collect regardless of the server flag. - CC server runtime flag — a server-controlled active/inactive state, surfaced as
activeAPIResponse=…,false,…in the legacy apps and relocated to aSettings.activecolumn 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:43access;permissions_grant_state.txtshows FINE+COARSE+BACKGROUNDgranted=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 whileactiveAPIResponse=…,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
- GPS was spoofed to each geozone centre (Android test providers). This alters the input,
not the subject: what is evidenced is each app’s own collection/upload behaviour. App binaries
were not modified or re-signed. Mock providers were removed at teardown (
mock_location=0). - On-device evidence only. The exact bytes TT transmitted were not packet-captured (already
flushed); egress is proven by the app’s own AWS-SDK
logcat+ the buffer flush, and Phase 3 documents the record shape (precise lat/lon + persistentdeviceId+ appKey). - Only TT yields a live positive. Dutch GP and Museumnacht are server-gated off at capture
time, so their negatives bound the test window, not a future active window — the capability is
fully present (DGP’s
[bg-s] 02:04access and queued record show it has run). - DGP/MN location permission was granted programmatically (
adb pm grant; noUSER_SET) to open the OS gate for the test; it simulates a user who has allowed location rather than reproducing a tap. This is the intended control (see Phase 3 §6). - Single device / single run. Screenshots were limited by a secure keyguard the operator
cannot unlock (
screenshots/device_after_bg_run.pngis the device state at end of run); all substantive evidence is the OS/app artifacts above.
7. Evidence set (off-repo, hashed)
Held in the capture evidence directory evidence/background-location/ with a SHA-256
MANIFEST.sha256 (18 artifacts):
SUMMARY.md,chain-of-custody.md,MANIFEST.sha256logs/bg_timeline.csv— poll-by-poll buffer + screen state + top activity (all 3 apps)logs/logcat_all.txt+logs/logcat_all_redacted.txt(STS creds/signatures masked)logs/dumpsys_location_after.txt(+ earlierdumpsys_location_full.txt) — standing registrationslogs/permissions_grant_state.txt— FINE/COARSE/BACKGROUND grant state for all three appslogs/background_watch.ps1,logs/bg_logcat.ps1— the exact capture drivers (reproducible)ttfestival/bg_egress_logcat.txt— PID-25675 background upload proof{ttfestival,dutchgp,museumnacht}/appops.txt+appops_after_bg.txt— OS location-access logsscreenshots/device_after_bg_run.png