leviathan d8670f4c32 v0.1.6 — audit fixes (critical, moderate, minor) + UX polish
Critical
--------
- DetectionService: subscribe to threatLevel + top event flows; rebuild the
  foreground notification on every change so a locked-screen user sees
  escalations. Vibrate on upward tier transitions (escalating waveforms for
  YELLOW/ORANGE/RED), gated by Settings.vibrateOnAlert (default on).
- DetectionService: only mark _running=true if at least one scanner started;
  stopSelf() if everything was disabled or denied. Switch START_STICKY →
  START_NOT_STICKY so a system-killed service doesn't re-create into a
  stuck "running but not scanning" state.
- DeflockClient: detect Overpass timeout-in-body (`{"remark": "...timed
  out..."}`) and treat as failure — previously these 200-with-empty-elements
  responses got cached for 24 h, hiding ALPRs in that 5×5 km cell for the
  next day.
- DeflockScanner: record lastFetch coords + timestamp on BOTH success and
  failure, with a 60 s backoff window after a failed attempt. Previously
  `lastFetchLat` was only set on Success, so every subsequent location
  update would re-trigger a 30 s POST that collectLatest then cancelled —
  we'd never finish a fetch under sustained Overpass slowness.
- LocationProvider: stale-lastLocation race fix. The async `lastLocation`
  callback now only seeds `_location` if it's still null and we're still
  running — previously it could overwrite a fresher fix from
  requestLocationUpdates, or fire after stop() and resurrect _location with
  stale data.

Moderate
--------
- CitizenScanner: wait for the first non-null location with .first { } before
  starting the poll/delay loop. First Citizen poll now fires within seconds
  of the location fix, not up to 60 s after.
- MainScreen: when not running, show a muted gray circle with "IDLE" text
  instead of the same solid green look as "scanning, all clear" — the
  pulse animation was the only differentiator before.
- Compose state: rememberSaveable for the screen enum + bottom-sheet open
  state, so SETTINGS survives rotation.
- MainActivity: detect permanently-denied permissions (the user picked
  "don't ask again") via shouldShowRequestPermissionRationale. UI swaps the
  call-to-action to "Open app settings" which fires
  Settings.ACTION_APPLICATION_DETAILS_SETTINGS. onResume re-checks so a
  user returning from app settings is reflected immediately.

Improvements
------------
- BLE/WiFi scanners record SourceHealth.OK on a successful start (and
  FAILED with a specific reason on every short-circuit — disabled adapter,
  missing permission, etc.) so the drill-down sheet is honest about radio
  state, not just network state.
- DetectionEvent gains optional lat/lon (populated by DEFLOCK and CITIZEN);
  SourceRow shows a tap-to-open-Maps icon next to events with coordinates,
  firing a `geo:lat,lon?q=lat,lon(label)` Intent.
- SettingsScreen sliders use onValueChangeFinished — only commit to
  SharedPreferences on drag-release, not on every pixel of movement.
- New Settings.vibrateOnAlert toggle (default on) wired to a SettingsScreen
  row under a new "Alerts" section.

Minor
-----
- BleScanner iterates ALL manufacturer-data entries to find XUNTONG; only
  falls back to the first entry if no XUNTONG match is present. Previously
  we only inspected the first entry.
- Drop dead `?.` on JSONArray.optString in CitizenClient (returns String,
  never null).
- Remove unused rememberCoroutineScope in MainScreen.
- Update stale Phase/Waze references in DetectionService comments.
- Add VIBRATE permission to manifest.

versionCode 6 → 7, versionName 0.1.5 → 0.1.6.
2026-04-28 22:11:56 -04:00
2026-04-28 21:56:27 -04:00

[DЯΣΛMMΛKΣЯ]

   . //0VΣЯW4TCH

A native Android (Kotlin) passive surveillance-detection app. Open it, hit START, and a circle turns green / yellow / orange / red depending on how confident the engine is that there's a Flock Safety ALPR, an Axon body camera, or police presence near you.

Passive defense only. OVERWATCH only listens — it does not transmit, probe, jam, or interfere with any device or network. The Axon advertise/fuzz code from one of the reference projects is intentionally excluded.


What it detects

Source What it looks at Where it comes from
BLE Bluetooth-LE advertisements: vendor MAC OUIs (Axon, Flock Penguin / Raven, XUNTONG mfg id 0x09C8, "TN" serial pattern), Raven service UUIDs, device-name patterns Local radio scan (BLE callback API)
WiFi BSSID OUI prefixes for Flock infrastructure (31-prefix superset), Flock-XXXX and other generic SSID patterns WifiManager.getScanResults() polled every 35 s (just under the Android 11+ 4-scans/2-min throttle)
DEFLOCK Crowdsourced ALPR locations within configurable proximity (default 200 m) POST to Overpass API (overpass.deflock.org → fallback overpass-api.de) for man_made=surveillance + surveillance:type=ALPR in a 5 km bbox; 24 h on-disk cache by 0.05° grid cell. Refetches when the user moves > 1.5 km from the last fetch center.
CITIZEN Real-time public-safety incidents (police-relevant only — fire/medical-only events filtered out) within configurable proximity, < 30 min old citizen.com/api/incident/trending (bbox) polled every 60 s, then per-incident detail via /api/incident/{id} with an in-memory cache so each incident is fetched once per session.

Why no Waze? Waze added reCAPTCHA gating to its live-map/api/georss endpoint in 2025/2026. Mobile clients receive HTTP 403, and the only known workarounds (Selenium proxy on a home server, Waze for Cities partner program) aren't viable for a phone-deployed app. Citizen replaces it.

Every observation is scored 0-100 by ConfidenceEngine. The on-screen tier is the maximum live score across all sources:

GREEN      < 40    nothing credible
YELLOW   40  69   single weak indicator
ORANGE   70  84   high confidence
RED        85 +    certain

The user-facing circle uses the full 4-tier mapping. Cross-source corroboration naturally pushes the global max upward (a BLE OUI hit and a DeFlock map match in the same area produce a higher tier than either alone).


Architecture

ui/MainScreen.kt              circle + START/STOP + tap-to-open bottom sheet
ui/SettingsScreen.kt          per-source toggles, distance sliders, theme
service/DetectionService.kt   foreground service — owns scanners + store
scan/BleScanner.kt            BLE callback scanner
scan/WifiScanner.kt           WifiManager poller + SCAN_RESULTS receiver
scan/DeflockClient.kt         CDN tile fetch + 24h cache
scan/DeflockScanner.kt        location-driven proximity check
scan/WazeClient.kt            live-map/api/georss bbox fetch
scan/WazeScanner.kt           60s poller + age/distance gate
fusion/ConfidenceEngine.kt    scoring (one place)
fusion/RssiTracker.kt         rise-peak-fall stationary-signal detector
fusion/DetectionStore.kt      in-memory dedup, 5-min retention
data/location/LocationProvider.kt  FusedLocationProviderClient wrapper
data/settings/Settings.kt     SharedPreferences-backed StateFlow settings
data/targets/                 BleOuis, WifiOuis, RavenUuids, Patterns, Manufacturers

No detection-history database. All state is in-memory and clears on stop, by design.


Build & install

Requires:

  • JDK 21 (Android Gradle Plugin 8.7.x rejects JDK 26)
  • Android Studio with SDK Platform 34 + Build-Tools 34.x + Platform-Tools
# 1) Copy the example local.properties and point sdk.dir at your install
cp local.properties.example local.properties
# edit local.properties → sdk.dir=/Users/<you>/Library/Android/sdk

# 2) Make sure JAVA_HOME is JDK 21
export JAVA_HOME=/usr/local/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home

# 3) Build & install on a connected device with USB debugging
./gradlew :app:installDebug

Or download the latest signed APK from Releases.


Permissions

Permission Why
BLUETOOTH_SCAN, BLUETOOTH_CONNECT (API 31+) BLE scanning
BLUETOOTH, BLUETOOTH_ADMIN (≤ API 30) BLE scanning, legacy
ACCESS_FINE_LOCATION Required for BLE pre-S, WiFi pre-T, and DeFlock proximity
NEARBY_WIFI_DEVICES (API 33+) WiFi scan results without using location
ACCESS_WIFI_STATE, CHANGE_WIFI_STATE Trigger and read scan results
INTERNET, ACCESS_NETWORK_STATE DeFlock CDN + Waze API
FOREGROUND_SERVICE, FOREGROUND_SERVICE_CONNECTED_DEVICE, FOREGROUND_SERVICE_LOCATION Keep scanning with the screen off
POST_NOTIFICATIONS (API 33+) Foreground-service notification

Requested at runtime when you press START for the first time.


Settings

Tap the gear icon in the top-right.

  • Detection sources: toggle BLE / WiFi / DeFlock / Waze independently. Takes effect on next Start.
  • Proximity thresholds:
    • DeFlock: 50 m 1600 m (default 200 m)
    • Waze: 100 m 5000 m (default 500 m)
  • Appearance: System / Dark / Light (default Dark)

Reference repos studied while building

These live under REFERENCES/ (gitignored):

  • AxonCadabra — BLE scanner skeleton (scan side only; advertise/fuzz code excluded)
  • flock-detection — confidence-scoring algorithm (highest reusability), RSSI rise-peak-fall, OUIs + UUIDs + patterns
  • flock-you — 31-OUI WiFi superset (promiscuous-mode tricks not portable to Android)
  • deflock + deflock-app — CDN tile scheme, proximity-alert pattern
  • wazepolice — live-map/api/georss recipe, Chrome header spoofing

Status

Phases 15 (skeleton, BLE, WiFi, DeFlock, Waze, polish) complete as of v0.1.0. Field-test-ready, not yet field-validated.

License

Personal use. Reference repos retain their own licenses; do not redistribute their code as part of this project.

Disclaimer

Tool for situational awareness about deployed surveillance infrastructure in public spaces. Local laws regarding electronic surveillance, RF monitoring, and police-tracking apps vary — your responsibility to know what's legal where you are.

S
Description
Mirror from github.com/KaraZajac/OVERWATCH
Readme 187 KiB
Languages
Kotlin 100%