Waze's reCAPTCHA gating on live-map/api/georss has no clean mobile
workaround, and the Citizen source added in v0.1.4 covers the same
threat model with better data. Keeping a permanently-failed source
visible was UI clutter — drop it.
Removed:
- scan/WazeClient.kt and scan/WazeScanner.kt (deleted)
- WAZE from DetectionSource enum
- waze flow from SourceHealth (+ flowFor/record/reset cases)
- WazeObservation + scoreWaze + W_WAZE_POLICE from ConfidenceEngine
- wazeEnabled from Settings (+ KEY_WAZE)
- WAZE row from SettingsScreen
- wazeScanner from DetectionService
Renamed (Citizen now owns the proximity slider that Waze used to share):
- Settings.wazeProximityM → citizenProximityM
- Settings.setWazeProximityM → setCitizenProximityM
- KEY_WAZE_PROX → KEY_CITIZEN_PROX
- DEFAULT_WAZE_PROX → DEFAULT_CITIZEN_PROX (still 500)
- SettingsScreen "Waze alert distance" → "Citizen alert distance"
Existing users will see the slider reset to 500 m default since the
SharedPreferences key changed.
versionCode 5 → 6, versionName 0.1.4 → 0.1.5.
Waze remains gated behind 2025/2026 reCAPTCHA on live-map; added Citizen
as a working alternative for police-presence signal. Citizen pulls from
911 + scanner traffic, returns rich incident data (lat/lon, timestamp,
severity level, responding precinct, title), and has no auth or
rate-limit gating.
New scan/CitizenClient.kt:
- GET /api/incident/trending (bbox query → list of incident ids)
- GET /api/incident/{id} (full detail per id)
- Sealed TrendingResult so the scanner can surface 4xx via SourceHealth.
New scan/CitizenScanner.kt:
- 60s poll interval, 30-min freshness window
- Per-id detail cache for the lifetime of a start/stop cycle —
incidents are immutable, so each is fetched at most once per session
- Title regex filter: drops pure fire/medical events that don't imply
police presence; retains them when the title also names police action
- Submits to the shared DetectionStore as DetectionSource.CITIZEN
ConfidenceEngine.scoreCitizen:
- Base 55 (matches the old W_WAZE_POLICE weight)
- +5 if level >= 2 (Citizen's own severity)
- +5 if title contains police-action keyword (police/officer/arrest/
swat/tactical/raid/pursuit/stop/search warrant)
Settings: new citizenEnabled toggle (default on); UI row in
SettingsScreen. SourceHealth has a new flow for CITIZEN. DetectionService
starts the scanner alongside the others when location is available.
Continued investigation of Waze / Google Maps police APIs:
- Waze SDK (hewliyang/waze-traffic-api): wraps the same blocked endpoint
- ddd/google_maps reverse-engineering: locations only, no incidents
- Google Maps Platform: no public incidents API (just displays Waze data internally)
- TomTom Traffic Incidents: traffic-only, no police presence
- Waze for Cities partner feed: real but requires being a city/police agency
versionCode 4 → 5, versionName 0.1.3 → 0.1.4.
The cdn.deflock.me CDN is gated behind Cloudflare bot mitigation that
mobile HTTP clients can't pass. The live deflock-app Flutter client
abandoned that path; it POSTs Overpass-QL queries directly to
overpass.deflock.org (with overpass-api.de as a fallback). Verified by
hitting the same endpoint from curl — 22 ALPRs returned for the
Springfield VA bbox, matching the user's screenshot of the working app.
DeflockClient rewrite:
- POST [out:json][timeout:25];(node[surveillance][type=ALPR](bbox););out body;
- 5 km half-width bbox around the user
- 24h on-disk cache keyed by 0.05° grid cell (revisits don't refetch)
- Returns sealed FetchResult: Success(points) | Failed(reason)
DeflockScanner update:
- Replaces 20° tile concept with distance-based refetch (1.5 km threshold)
- Records SourceHealth on each fetch outcome
Waze: reCAPTCHA gating confirmed. WazeClient.fetchPoliceNear now returns
sealed FetchResult; WazeScanner records SourceHealth.FAILED with
"Upstream blocked (HTTP 403)" so the user sees why no Waze data is
flowing instead of silent zeros.
New fusion/SourceHealth.kt — per-source MutableStateFlow registry,
record(source, ok, message) + reset() called on service start/stop.
UI: SourceRow in the bottom-sheet drill-down now shows the health
message in orange when status = FAILED instead of "no detections".
versionCode 3 → 4, versionName 0.1.2 → 0.1.3.
Critical:
- DetectionService.startInForeground now passes
FOREGROUND_SERVICE_TYPE_LOCATION OR'd with TYPE_CONNECTED_DEVICE on
Android 14+. Without this, the system silently revoked location access
once the screen locked, breaking DeFlock + Waze for foreground-service
use (the whole point of the foreground service).
- DeflockClient and WazeClient now skip JSON entries whose lat/lon parse
to NaN. Previously NaN flowed into Location.distanceBetween, the
NaN > limit check returned false (IEEE 754), and we submitted a
full-confidence detection labeled "@0m" — instant false-positive RED
from a single malformed map entry.
UX:
- First-run permission flow auto-starts scanning after the user grants
everything; no second tap on START required.
- Settings shows a "Restart scan to apply" button when toggling sources
while scanning. Source toggle changes used to silently no-op until
the next manual stop+start.
versionCode 1 → 3, versionName 0.1.0 → 0.1.2.
The button was gated on `granted || running`, but the only thing that
triggers the permission request is tapping the button — catch-22 that
left first-time users with no way to grant permissions.
Always enable the button when not running; the onStartStop handler already
routes correctly (start scanning if granted, else launch the permission
request flow). Updated the helper text to point at this directly.