Commit Graph
48 Commits
Author SHA1 Message Date
KaraZajacandClaude Opus 5 46aa2e5591 v0.5.15 — second Waze backend: the app protocol, no API key (opt-in)
Until now Waze cost money. The public live-map/api/georss endpoint every
scraper used is fronted by Google's edge and returns HTTP 403 to automated
clients regardless of IP, headers, TLS fingerprint or headless-vs-headful
browser — Waze's own page requests included — so the only way in was OpenWeb
Ninja's hosted feed at roughly $0.005/request.

There is another way in: the protocol the Waze app itself speaks. OVERWATCH
can now register an anonymous Waze account (Waze mints the credentials on
request) and query alerts directly. Free, keyless, live, and polled every 60 s
instead of every 4 min.

It is off by default and stays off until the user picks it, because it is not
a free lunch: the app becomes a Waze client. It holds a Waze account and sends
a position — the protocol layer blurs it by up to 500 m, but it is still
roughly where you are — to a Google service on every poll. In an app whose
whole purpose is knowing who is watching you, that has to be an explicit
choice, so the Settings screen states it in those terms rather than burying it.
OpenWeb Ninja stays the default and is unchanged.

Shape:
- scan/WazeSource.kt is the backend interface. Both clients implement it and
  each sets its own poll cadence, so WazeScanner no longer knows or cares
  which one it is talking to, and neither leaks into scoring or the UI.
- scan/WazeClient.kt (OpenWeb Ninja) is behaviourally unchanged; its alert and
  result types simply moved to the interface.
- scan/wazert/WazeRtFetcher.java is ours: session lifecycle, the encrypted
  anonymous account, the per-day registration cap and the backoff.

Vendored, not written here: scan/wazert/*.java and app/src/main/proto/
waze.proto are the Waze RT protocol layer from highway-radar-sabre-plus (MIT),
kept with its licence beside the code. Changed only in the package name, an
OkHttp-to-HttpURLConnection swap so the app takes no new dependency, and the
removal of the report-submission path — OVERWATCH reads alerts and never
reports one, and the codec's report builder is deleted so it cannot.

Protocol behaviour worth recording, all verified live rather than read from
source (2026-09-21, from a Linux host and an Android 16 emulator):
- The session is stateful. /command sends each alert once, then a removal as
  an old_command string "RmAlert,<uuid>", not a message type. Treat a response
  as a snapshot and the feed goes empty after the first query, so responses
  merge into a cache with a 5-minute soft-delete.
- A fresh account's first /command almost always returns an in-band
  ServerError{504, "Retry"} inside an HTTP 200. It succeeds on retry, so it is
  absorbed rather than counted as a failure.
- Login sets a Waze-Session-Affinity cookie every later request must carry,
  and the session idles out after ~100 s.
- Longitude arrives as unsigned 32-bit micro-degrees and must be mapped back
  to signed, or the western hemisphere lands on the wrong side of the planet.
- Data volume inverts the usual instinct: ~195 KB for a session's first
  response, ~8 KB per query after. Polling faster (60 s, under the idle
  timeout) uses less data than polling slowly, because a re-login re-sends the
  whole viewport. Hence the cadence.
- Registration is capped per device per day, so the account is persisted
  encrypted via SecureStore and reused; a rejected one backs off 30 s → 10 min
  instead of spinning the register loop.

Build: applies com.google.protobuf 0.10.0, the first release compatible with
AGP 9 (earlier ones bind to the removed applicationVariants API), with protoc
and protobuf-javalite 4.35.0. On Android the plugin creates no "java" builtin,
so the lite generator is requested via maybeCreate. Costs ~0.6 MB of APK.

Verified end to end on an Android 16 emulator, not just in a harness:
registered an account, logged in, queried, and raised a real detection —
"Police report @ 250m, 16min ago", score 54, YELLOW. Cross-checked against a
live query: the one report inside the 2 km evaluation radius was 58 minutes
old, past the 45-minute cutoff, and the app correctly showed all-clear until
the position moved next to a fresh one.

Also: radio rows in Settings are tappable across their full width instead of
only on the 20 dp circle, which also fixes the theme picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-21 22:55:45 -04:00
KaraZajacandClaude Fable 5.1 d8c916f25c v0.5.14 — smart-glasses identifiers from Nearby Glasses, Sonos mislabel fix
Extends the GLASSES family of the COMMERCIAL source from the identifier set
curated by Nearby Glasses (Yves Jeanrenaud, github.com/yjeanrenaud/
yj_nearbyglasses, AGPL-3.0). What is taken is the project's published
identifiers — facts about the radio protocol — not its code, and every
company id was re-verified against the Bluetooth SIG assigned-numbers
registry (4,035 entries) before landing.

New coverage:
- HeyCyan-SDK primary service UUID 7905FFF0-B5CE-4E99-A40F-4B1E122D00D0,
  which identifies the Nilox Smart AI Glasses (ALDI/Hofer) and other
  HeyCyan-based frames. It is fixed on the software side, so it survives the
  randomised MACs and unstable names that defeat most glasses detection.
- Service UUIDs are now read from advertised service-data keys as well as the
  service list; HeyCyan frames have been seen carrying it either way.
- Case-insensitive name tokens rayban / ray-ban / ray ban / heycyan, kept
  separate from the case-sensitive hint list so short generic words stay
  strict.
- The HeyCyan and Echo AVS service UUIDs join the screen-off ScanFilter set,
  ahead of the company ids, so glasses can be caught with the phone in a
  pocket. COMPANY_IDS is reordered glasses-first for the same reason: with
  the 16-slot cap, what falls off the end is now a fixed home speaker, not a
  body-worn camera. Verified on Android 16: 19 filters trim to 16, no scan
  failures.

Deliberately not taken: 0x05D6 (Zhuhai Jieli). Nearby Glasses lists it for
the Rogbird VisionPro and Rollme VistaView, but it is the id of the Jieli
JL70xx Bluetooth chipset found in a huge share of cheap earbuds and speakers;
matching it would label every one of them "Smart glasses". Same rationale as
the existing TCL exclusion. Documented in MicTargets, README and SOURCES.md.

Also fixes a pre-existing mislabel surfaced by the registry check: 0x05A7 is
Sonos Inc, not "Yingxin / cheap-spy-cam". It was mapped to HIDDEN_CAM and
would have tagged a Sonos speaker as a hidden camera. Sonos stays in scope
(the Era/One lines carry always-on microphones) under a new SONOS family that
reads "Sonos speaker".

Credit added to README (reference repos), SOURCES.md §3.7 and the showcase
site. Two small upstream notes recorded for passing back: the CSV's Snap row
carries 0x0D53 (Luxottica; the project's code has Snap correctly as 0x03C2),
and its scanner reads only the first manufacturer-data entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 20:59:55 -04:00
KaraZajacandClaude Opus 5 7b751eb19d v0.5.13 — fix the map returning at world zoom after a stop/start
Reported from a real device with a screenshot of the circle showing the entire
world map tiled. A regression from v0.5.9, and a textbook one.

The MapCamera guard that stops a redundant zoomToBoundingBox on every data
change was remembered in the *outer* composable, but the MapView it describes
lives in an inner branch: stopping a scan clears the location, the branch swaps
to the idle disc, and the MapView is destroyed. On the next start a brand-new
MapView comes up at osmdroid's default zoom — the whole world — while the
outer remember still held the old camera position, so needsMove() compared
unchanged lat/lon/radius, returned false, and the new map was never zoomed in.

Remembered state has to share a lifetime with the thing it describes, so the
camera now lives inside the branch that owns the map, in both the main circle
and the overlay bubble.

Reproduced deterministically before fixing: across four stop/start cycles on
the shipped v0.5.12 the rendered screen was 405 KB on the first cycle and
~171 KB on every one after — the low-detail world view. After the fix, five
consecutive cycles render 405-412 KB with street-level detail and the
crosshair centred.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-17 10:07:01 -04:00
KaraZajacandClaude Opus 5 d3e4a1a5cd v0.5.12 — decouple scoring from the range control entirely
Reported from a real device: with "show within" at 1100 m the circle was
YELLOW on a police report 312 m away scoring 52; dragging to 300 m turned it
GREEN and said "All clear". Same spot, same police car.

v0.5.10 fixed the widening direction by calibrating the curves, but the
narrowing direction was still broken and is worse — a view setting that can
hide a live alert, and silently turn the circle green while it does, is a trap
rather than a feature. The earlier verification swept the slider at a location
where the nearest thing scored 35, so it could not have caught this.

Root cause was structural, not calibration: the scanners used the user's
setting as their emit cutoff, so it decided what entered the store, and the
store decides the tier. Scanners now evaluate at their own fixed radii
(DeFlock 1200 m, Waze 2000 m; aircraft already had its own), DetectionEvent
carries the distance it was observed at, and the UI filters only what it
*draws and lists*. Anything at YELLOW or above is shown regardless of range.

Removes the now-dead refresh() paths and the jobs that drove them, since a
range change no longer needs to touch a scanner.

Verified on device: a camera 285 m away scoring 48 holds the tier at YELLOW
from a 200 m view range through 4900 m, both out-of-range alerts stay listed
rather than vanishing, and standing 29 m from a camera still reads 89 RED.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-17 08:44:42 -04:00
KaraZajacandClaude Opus 5 aa53757a1a docs: correct README version drift and add the v0.5.11 entry
The header and "Current release" lines had silently read v0.5.7 since that
release: three version bumps used an unguarded string replace whose pattern no
longer matched, so they no-op'd without failing. Both now read v0.5.11, the
v0.5.11 changelog entry is present, and the build section records that this is
tested on Android 16 with a punch-hole cutout as well as Android 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-17 00:18:07 -04:00
KaraZajacandClaude Opus 5 76faa840b1 v0.5.11 — Android 15/16 + display-cutout compatibility, BLE screen-off fix
Two separate compatibility bugs, both reproduced before fixing.

**Edge-to-edge.** Android 15 draws apps edge-to-edge whether or not they ask
for anything targeting SDK 35, and the statusBarColor/navigationBarColor theme
attributes this app relied on became no-ops at the same time. Reproduced on a
fresh Android 16 emulator with a punch-hole cutout: the shipped v0.5.11-1 drew
"OVERWATCH" inside the status bar next to the clock, buried the settings gear
under the wifi and battery icons where it could not be tapped, and jammed the
permission hint into the gesture bar. MainActivity now calls enableEdgeToEdge()
explicitly — so the behaviour is the same on older releases rather than
shifting under the user on an OS upgrade — and both screens pad themselves with
WindowInsets.safeDrawing, which covers the system bars and the cutout. The
background still runs edge to edge; only content is held clear. The overlay
bubble states LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT rather than relying on it,
since a draggable free-floating window could otherwise park under a camera hole.

**BLE with the screen off.** Since Android 8.1 the Bluetooth stack stops
delivering results for scans started with no ScanFilter once the screen turns
off, and a foreground service does not exempt it — it is a stack rule, not a
process-lifetime one. This app called startScan(null, ...) while promising to
keep watching from a pocket, so BLE detection was silently dead in exactly the
case that matters. A ScanFilter cannot express an OUI prefix, so the primary
MAC-prefix method genuinely cannot run with the screen off; what can be named
precisely still can. The scanner now switches on ACTION_SCREEN_ON/OFF:
unfiltered while the screen is on, and a filtered scan (Raven service UUIDs,
the XUNTONG manufacturer id, mic-target company ids) while it is off, capped at
16 filters because slots are a hardware resource and a silently empty scan is
the worst failure this app has. The drill-down says so rather than hiding it.

Verified on Android 16 (API 36) and Android 14: foreground service starts with
types=0x18, all five scanners run, screen off logs "filtered scan (16 filters)"
and screen on returns to unfiltered, zero scan failures, zero crashes.

SOURCES.md gains a platform-constraints section covering these plus the BLE
5-starts-per-30s limit, WiFi scan throttling, and WifiManager.startScan()'s
deprecation and planned removal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-17 00:13:46 -04:00
KaraZajacandClaude Opus 5 19622b87e1 v0.5.10 — recalibrate distance curves so range can't move the threat tier
The scores were already absolute, but the tier is max(score) over everything
*reported* and the range slider decides what is reported — so the setting
still leaked into the alarm. Measured against a real 195-node cache: standing
still and dragging the slider from 300 m to 500 m flipped the app from GREEN
to YELLOW with nothing physical changing.

The fix is calibration, not plumbing. Every curve now crosses below the YELLOW
line at roughly the distance the thing stops being able to act on you. A Flock
camera reads plates at ~30-50 m, so it is RED on top of it, ORANGE at 100 m,
and GREEN by 500 m — still drawn on the map, just no longer an alarm. Waze
keeps a wider band deliberately: a police car covers 700 m in under a minute,
a bollarded camera never moves. Aircraft are unaffected, since that scanner
uses its own ranges rather than the slider.

The main-screen slider is relabelled "show within" to say what it actually is
— a view control, not a sensitivity control.

Verified on device: sweeping 200 m -> 4900 m leaves the tier at GREEN while
the reported count goes 0 -> 133, and standing 29 m from a real camera reads
89 RED, then 50 at 252 m and 41 at 373 m. Scores match the falloff formula
exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 23:47:20 -04:00
KaraZajacandClaude Opus 5 32e2ef2606 v0.5.9 — detection radius on the main screen, source-colour legend
The radius is the one setting a user reaches for while actually moving, so it
no longer lives behind the gear icon: it sits under the map circle, where it
also fills what was dead space in the bottom-pinned layout. Commits on release
rather than per-pixel, so dragging it doesn't restart the location scanners on
every frame. Removed from Settings rather than duplicated.

Added a legend under the circle, because five classes now share it and the
colours were otherwise just decoration: ALPR red, speed camera amber, generic
camera gray, Waze police blue, aircraft violet, plus the user crosshair.

Verified on device: dragging to 2500 m rezooms the map and takes the DeFlock
detection list from 1 entry to 57, with graded scores (49 @ 494 m, 46 @ 567 m,
46 @ 582 m) — so the change reaches the scanners, not just the map. No crashes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 23:31:42 -04:00
KaraZajacandClaude Opus 5 da97924ad2 v0.5.8 — AIRCRAFT source, wider Overpass query, distance-graded scoring
Adds a sixth source: police and surveillance aircraft overhead, via the free
community ADS-B networks (opendata.adsb.fi, api.adsb.lol fallback, no API
key). Contacts are matched by ICAO 24-bit address against a bundled registry
of 1,971 US law-enforcement airframes generated by scripts/gen-le-aircraft.py
from ADSBexchange's basic-ac-db and committed as res/raw/le_aircraft.csv.

Community feeds are used deliberately: FlightAware and Flightradar24 filter
law-enforcement flights at government request, which is the traffic this
source exists to see. Two parse traps handled — the envelope is {"ac":[..]}
on radius queries but {"aircraft":[..]} on wider ones, and alt_baro is the
string "ground" when parked.

Registry coverage is imperfect, so the scanner also detects loiter/orbit
behaviour (3+ samples over 4+ min within 5 km of their centroid, below
12,000 ft and under 200 kt). Surveillance aircraft circle; airliners and
medevac flights going somewhere do not. Behaviour-only hits are capped at 69
and must be within 8 km, so ordinary traffic never alerts.

Rejected with measurements: plane-alert-db matched 0 of 394 live aircraft
over a 250 nm sweep of DC while 15 helicopters were aloft (only 255 of its
entries are US-registered); registry.faa.gov is Akamai-403 and N-number
keyed; OpenSky's CSV is 94 MB for the same data; EFF's Atlas of Surveillance
has no aircraft identities at all, only a 2022 FAA drone lookup.

Overpass query widened from ALPR-only to man_made=surveillance plus
highway=speed_camera, classified into ALPR / speed camera / generic camera
with separate falloff curves so a shop's CCTV cannot alarm like a Flock
install. Cache key bumped to deflock2_ so v1 ALPR-only entries are not served
for another 24 h.

DeFlock, Waze and aircraft are now scored by continuous distance falloff
rather than flat values. The anchors are absolute metres and deliberately
independent of the detection-radius setting: moving a slider must not move
the threat level.

New SOURCES.md documents every endpoint, identifier and scoring table,
including the sources that were tried and rejected and the measurements that
killed them.

Verified against live traffic: detected San Diego PD's AS50 at 1580 m /
1000 ft scoring 83 (ORANGE) and a San Diego County Sheriff B407 at 11742 m
scoring 44, with the score tracking the helicopter from 81 to 83 as it
closed. Both match the falloff formula exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 23:20:53 -04:00
KaraZajacandClaude Opus 5 fa75c624b1 ci: stop installing the removed 'tools' SDK package
setup-android defaults to packages "tools platform-tools", but Google has
removed the obsolete `tools` package from the SDK repository, so sdkmanager
exits 1 with "Failed to find package 'tools'". Pass packages: '' — AGP
downloads the platform and build-tools it needs, and this step only has to
provide sdkmanager and accept licenses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 21:49:16 -04:00
KaraZajacandClaude Opus 5 97c37bcdf5 ci: pin setup-android back to v3
v4 ignores the runner's preinstalled cmdline-tools (12.0), downloads 20.0,
and its license auto-accept then fails with "6 of 7 SDK package licenses not
accepted" followed by an interactive prompt. It passed on 2026-08-29 and
broke by 2026-09-17, so the trigger is a runner-image change rather than the
workflow. v3 is known-good for this repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 21:48:09 -04:00
KaraZajacandClaude Opus 5 0606e1f1ac v0.5.6 — Waze: call OpenWeb Ninja directly with the user's own API key
Drops the api.blackflagintel.com proxy. Routing everyone through one shared
credential meant handing out a token that wasn't theirs and billing every
install to one account; now each install authenticates with its owner's own
OpenWeb Ninja key, entered in Settings and stored encrypted via SecureStore
(Keystore AES/GCM). Nothing is baked into the APK — verified against the
built dex: the endpoint and X-API-Key header are present, no key, no
blackflagintel host, no X-App-Token.

Requests now send alert_types=POLICE&max_jams=0. Both are honored server-side
as of 2026-09-16 (alert_types was ignored when this was first written), which
cuts a typical response from ~18 KB to ~1.5 KB. max_alerts stays at the 200
ceiling and the client still filters by type, so a silent upstream revert
can't crowd POLICE out or let other types through.

Settings: wazeProxyToken -> wazeApiKey under a new storage key; the stale
proxy token is purged from the secret store on first run rather than left
behind. Auth failures now read "Invalid or missing API key (HTTP 401)".

Verified live with a real key: the exact URL the client builds (including
URLEncoder's %2C bbox commas) returns 200 with POLICE alerts, and on-device
the key persists through SecureStore and reaches the client
(WazeScanner: configured=true). The fetch->parse->emit leg could not be
exercised on the emulator: it delivers no location fix (last location=null,
shell denied MOCK_LOCATION), which blocks every location-driven source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-09-16 21:46:53 -04:00
Kara ZajacandClaude Opus 5 39b68b0946 footer: publish the site's .onion address
The Onion-Location header already offers the mirror to anyone browsing in Tor
Browser. This is for everyone else — someone in an ordinary browser, who
otherwise has no way to learn the onion exists.

The address is printed in full rather than hidden behind a "Tor" link, because
a .onion link is a DNS failure outside Tor Browser; the string itself is the
useful artifact. currentColor and opacity so it inherits each footer's existing
colour instead of needing per-site theme variables, user-select:all so one
click selects the whole thing, and word-break so 56 characters wrap rather than
overflow on a phone. No JS, so it survives Tor Browser's Safest level.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRcCTSYyyiYiTzTwMyfQb6
2026-08-31 14:42:47 -04:00
Kara ZajacandClaude Opus 5 8f53ce1552 fonts: self-host Inter, JetBrains Mono and Space Grotesk
These sites are mirrored as Tor onion services, and a webfont pulled from
Google left the network through an exit node on every page load — telling a
third party about a visitor who chose an onion precisely so that would not
happen.

Variable fonts, latin + latin-ext: one file per subset covers every weight the
site uses, so it is one request instead of five, which matters more over Tor
than on clearnet. latin-ext is only fetched when a page actually contains such
a character. All three families are OFL-1.1, which permits redistribution;
see fonts/OFL.txt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRcCTSYyyiYiTzTwMyfQb6
2026-08-31 13:45:34 -04:00
KaraZajacandClaude Fable 5 b39cb6c13f Drop the kotlin.android alias from the root build file too
AGP 9 rejects the standalone Kotlin plugin, so v0.5.5 removed the alias from
app/build.gradle.kts and the version catalog — but the root build file also
declared it with `apply false`, leaving an unresolved catalog reference that
only CI hit. The local build had passed on a stale configuration-cache entry
that never recompiled the root script; verified here with a clean build and
--no-configuration-cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-08-28 21:26:59 -04:00
KaraZajacandClaude Fable 5 0f8e6ada22 v0.5.5 — Citizen feed retired upstream; toolchain modernized
Citizen ended the police-dispatch partnership behind its public feed in
June 2026 and stubbed the endpoints: /api/incident/trending returns a bare
JSON empty string, /api/incident/{id} returns {} for any id, and
data.sp0n.io/v1/incidents/trending returns a zero-byte body even with no
params. Passing that "" to JSONObject is what surfaced "Value of type
java.lang.String cannot be converted to JSONObject" in the drill-down.

CitizenClient now models the stub as TrendingResult.Retired instead of
throwing, keeping the "results present but empty" case a real Success, and
CitizenScanner reports the shutdown plainly and backs off to a 30-min
heartbeat rather than polling a dead endpoint every 60 s.

Toolchain: AGP 9.3.2, Gradle 9.7.1, Kotlin 2.4.10, Compose BOM 2026.08.00,
core-ktx 1.19.0, lifecycle 2.11.0, activity-compose 1.13.0,
play-services-location 21.4.0, compileSdk 37. targetSdk stays 35 on purpose
— API 36+ tightens foreground-service behavior and screen-off scanning is
the core feature. AGP 9 bundles Kotlin, so the standalone kotlin.android
plugin is dropped. CI actions bumped off deprecated Node-20 versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-08-28 21:22:59 -04:00
KaraZajacandClaude Fable 5 91aad52daa v0.5.4 — 18 surveillance-vendor OUIs across BLE + WiFi
IEEE-verified vendor registrations (ShotSpotter, WatchGuard/Motorola,
Verkada, Avigilon Alta, Axis, FLIR, Hanwha, March Networks, GeoVision,
Mobotix, Sunell) added to both radio target tables via a shared
VendorOuis map with per-vendor drill-down labels. Police-exclusive
vendors (WatchGuard Video, ShotSpotter) score ORANGE on sight — same
rationale as the Axon OUI. README + showcase page updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
2026-08-28 20:56:21 -04:00
Kara ZajacandClaude Opus 4.8 f958e30653 Drop the DREAMMAKER byline from the footer
OVERWATCH stands on its own — remove the "DREAMMAKER project" attribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173RhsXkdMViDR8DMNAG3ms
2026-07-15 20:39:33 -04:00
KaraZajacandClaude Fable 5 fb4502f79a docs: clean up the README header (drop DREAMMAKER branding)
Replaces the stylized "[DЯΣΛMMΛKΣЯ] // 0VΣЯW4TCH" header block with a plain
"# OVERWATCH" title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-15 20:36:33 -04:00
Kara ZajacandClaude Opus 4.8 869726ce19 Cache-bust style.css so the new .shots gallery rules load
Cloudflare was serving a stale cached style.css without the screenshots
gallery CSS; version the link (?v=2) to fetch the fresh stylesheet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173RhsXkdMViDR8DMNAG3ms
2026-07-15 19:18:13 -04:00
KaraZajacandClaude Fable 5 66b58891a3 docs: add a Screenshots section to the README landing page
Shows the three app screens (main / sources / settings) on the repo's front
page, reusing the existing docs/img assets. Complements the showcase site's
gallery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-15 19:13:20 -04:00
Kara ZajacandClaude Opus 4.8 7d85dec970 Add screenshots gallery to the OVERWATCH showcase page
New "Screens" section (3-up phone gallery: threat ring, source
drill-down, settings) with captions, plus a nav anchor and .shots CSS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173RhsXkdMViDR8DMNAG3ms
2026-07-15 19:09:10 -04:00
Kara ZajacandClaude Opus 4.8 98b6769256 docs: adopt the radar icon + v0.5.3 (smart-glasses detection)
Swaps the ◉ glyph for the new radar logo (nav, footer, favicon, OG card),
bumps to v0.5.3, and notes smart-glasses (Meta/Snap/Vuzix) in the Commercial source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 18:55:07 -04:00
KaraZajacandClaude Fable 5 ae6ea536fc v0.5.3 — smart-glasses detection + radar icon
versionCode 18 -> 19, versionName 0.5.2 -> 0.5.3. README: COMMERCIAL source now
lists smart glasses; changelog updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-15 18:29:39 -04:00
KaraZajacandClaude Fable 5 7eb3f2e1b3 feat: radar app icon (launcher + themed + notification)
Radar logo — range rings, crosshair, sweep beam, red contact blip on the dark
screen. Adaptive foreground + a monochrome themed-icon layer + a matching
notification small icon (was the generic system ic_menu_view).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-15 18:28:54 -04:00
KaraZajacandClaude Fable 5 c730657605 feat: detect Meta/Snap/Vuzix smart glasses in the COMMERCIAL source
Camera-bearing smart glasses via Bluetooth SIG company IDs (Meta 0x01AB, Meta
Platforms Technologies 0x058E, Luxottica 0x0D53, Snap 0x03C2, Vuzix 0x060C) —
the same manufacturer-id vector used for Echo/Nest and the one Nearby Glasses
relies on. RayNeo/XREAL/Rokid (no dedicated SIG id) matched by BLE-name hint.
New Family.GLASSES; COMMERCIAL toggle label updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-15 18:28:54 -04:00
KaraZajacandClaude Fable 5 610d6161e8 docs: link the homepage (overwatch.netslum.io) in the README
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-13 22:51:51 -04:00
Kara ZajacandClaude Opus 4.8 84b1de016b docs: add OVERWATCH showcase page (overwatch.netslum.io)
Static landing page (index.html + style.css + app.js + og) reusing the
SKELETONKEY project-page design system, tailored to the Android app:
hero + threat-circle screenshot, six detection-source cards, the
green/yellow/orange/red tier model, passive-only ethics, and a
sideload guide. Canonical = overwatch.netslum.io.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:03:11 -04:00
KaraZajacandClaude Fable 5 9314814ea4 v0.5.2 — commit fixed debug keystore for stable signing
Every CI build previously minted a fresh debug keystore, so each release failed
to install over the last (signature mismatch) — forcing an uninstall and loss of
the on-device proxy token. Commit a fixed debug.keystore (a debug key is
non-secret; password is the well-known "android") and point the debug
signingConfig at it, so CI and local builds sign identically and updates install
in place. No functional app changes.

versionCode 17 -> 18, versionName 0.5.1 -> 0.5.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-13 17:11:07 -04:00
KaraZajacandClaude Fable 5 7671e8fef8 v0.5.1 — UI: map ring, ⌖ crosshair, source-color dots, bottom START
- Larger main-screen map circle (220 -> 300dp) wrapped in a threat-color ring
  (green/yellow/orange/red while scanning, gray idle).
- User position renders as a ⌖ crosshair; map geodata color-coded by source
  (Flock/DeFlock red, Waze blue, Citizen purple) in both the main circle and
  the floating overlay bubble.
- START button moved to the bottom of the screen.
- README brought current: COMMERCIAL source + overlay documented, Waze-via-proxy
  and the CI release pipeline described, architecture/permissions/changelog
  updated, build reqs corrected to JDK 17 / SDK 35.

versionCode 16 -> 17, versionName 0.5.0 -> 0.5.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-13 17:03:06 -04:00
KaraZajacandClaude Fable 5 ef68394080 ci: build debug APK and publish release on tag via GitHub Actions
release.yml builds :app:assembleDebug (JDK 17 + Android SDK) and uploads the
APK as an artifact on every run; on a v* tag push it also creates a GitHub
Release with the APK attached. No build-time secrets — the app carries no
key/token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-13 00:26:07 -04:00
KaraZajacandClaude Fable 5 26c4248282 v0.5.0 — Waze police source via key-protected proxy
Re-adds the WAZE detection source (removed in v0.1.5 when Waze reCAPTCHA-gated
its live-map endpoint). Data comes from OpenWeb Ninja's hosted Waze feed, but
the paid API key never ships in the app: a Caddy reverse proxy at
api.blackflagintel.com injects the key server-side, and the app authenticates
with a scoped X-App-Token entered in Settings and stored encrypted via the
Android Keystore (SecureStore). No credential is baked into the APK.

- scan/WazeClient.kt, scan/WazeScanner.kt: proxy client + 4-min poller,
  200-alert page + client-side POLICE filter, 45-min freshness window.
- data/settings/SecureStore.kt: Keystore AES/GCM at-rest store (no dependency).
- Settings: encrypted wazeProxyToken + "Waze police feed" token field.
- ConfidenceEngine.scoreWaze, SourceHealth/DetectionSource WAZE, service wiring.
- Removes the BuildConfig/local.properties key baking entirely.

versionCode 15 -> 16, versionName 0.4.0 -> 0.5.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jgJnzMr2bdNuWcDX2xRUR
2026-07-13 00:26:07 -04:00
KaraZajac fc67d3d203 v0.3.2 — fix overlay crash on first show
v0.3.1 introduced TouchInterceptor (a FrameLayout wrapping the
ComposeView) so we could intercept touches before the inner MapView
saw ACTION_DOWN. That made the wrapper the window-root view (the
View actually attached to WindowManager) but I left the
ViewTreeLifecycleOwner / ViewTreeSavedStateRegistryOwner tags on
the inner ComposeView.

Compose's WindowRecomposer.create looks up findViewTreeLifecycleOwner
on the window-root, not on the inner ComposeView. With the tag
missing on the wrapper, Compose throws IllegalStateException at
composition startup — service crash on overlay enable.

Fix: move setViewTreeLifecycleOwner + setViewTreeSavedStateRegistryOwner
to the wrapper. v0.3.0 worked because the ComposeView itself was the
window-root then; the same pattern (owners on whatever's attached
directly to WindowManager) holds here.
2026-05-07 23:52:57 -04:00
KaraZajac eb26def14b v0.3.1 — overlay drag fix + drag-to-dismiss zone
- Bug: dragging the bubble panned the OSM map instead of moving the
  bubble. The OnTouchListener was attached to the ComposeView, but
  the inner MapView consumed ACTION_DOWN for its own pan handling
  before the listener fired. Fix: wrap the ComposeView in a custom
  TouchInterceptor FrameLayout whose onInterceptTouchEvent always
  returns true. Touches go to the wrapper's OnTouchListener; child
  views (including MapView) never see them. Bubble is now purely a
  visualization — pan/zoom is impossible.
- Drag-to-dismiss: when the user starts dragging (after passing
  TAP_SLOP), a translucent dark circle with a white X appears at
  bottom-center via a separate WindowManager view. Highlights red
  when the bubble's screen-space center is within hit slop of the X.
  Releasing on the X tears down the bubble AND fires onDismissed —
  DetectionService flips setOverlayEnabled(false) so the toggle and
  the bubble state stay in sync. Releasing elsewhere is a normal
  drag (just repositions). The dismiss zone uses FLAG_NOT_TOUCHABLE
  so it never steals the gesture; it's purely visual feedback.
2026-05-07 23:45:01 -04:00
KaraZajac 0841a0a33f v0.3.0 — floating threat-circle overlay (chat-bubble style)
A 140dp draggable bubble shows the same map / tier scrim / user dot /
ALPR dots that the in-app circle does, on top of any other app, while
scanning is on. Tap = brings the host app forward; drag = repositions.

- Manifest: add SYSTEM_ALERT_WINDOW (special-access — granted via
  system Settings page, not the runtime prompt).
- Settings: add overlayEnabled flag (default off) + a "Display over
  other apps" section in SettingsScreen. Flipping the toggle to on
  fires Settings.ACTION_MANAGE_OVERLAY_PERMISSION so the user can
  grant via the system page; if they deny or revoke, the OverlayMgr
  re-checks canDrawOverlays() at every show() call and silently
  no-ops, no crash.
- New OverlayManager: owns the WindowManager view at
  TYPE_APPLICATION_OVERLAY with FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
  so touches outside the bubble pass through and the bubble never
  steals IME focus. Custom OverlayOwner implementing LifecycleOwner +
  SavedStateRegistryOwner since LifecycleService doesn't satisfy SSR
  (Compose's ComposeView requires both via the view tree).
- Drag/tap handler at the View layer: rawX/rawY math for the drag,
  TAP_SLOP_PX guard to discriminate tap from drag, tap launches
  MainActivity (FLAG_ACTIVITY_NEW_TASK | SINGLE_TOP).
- New OverlayBubble composable: smaller (140dp) self-contained version
  of the in-app threat circle that pulls running/threat/location/
  mapPoints/proximity from the same companion StateFlows. Shared
  dot-drawable helper extracted into ui/MarkerIcons.kt.
- DetectionService observes settings.overlayEnabled in beginScanning
  and toggles the overlay; endScanning hides it.
2026-05-07 23:24:56 -04:00
KaraZajac 42f657bc0a v0.2.2 — live proximity refresh + map dot markers + COMMERCIAL row fit
- Map markers: replace osmdroid's default teardrop pin (which reads as
  a 'click me' affordance the map doesn't actually offer) with simple
  red dots for ALPRs, matching the blue user-position dot. Drawables
  share a single dotDrawable() helper.
- Live re-eval on proximity slider change. Bug: moving the DeFlock or
  Citizen distance slider while scanning updated the map's visible
  radius but didn't trigger a re-evaluation of which detections fire,
  so events outside a tightened radius lingered until restart and
  events inside a widened radius wouldn't appear until the next
  fix/poll cycle. Fix: add DetectionStore.clearSource(), add
  refresh() to DeflockScanner + CitizenScanner that clear the source
  and re-emit against cached state, observe the proximity StateFlows
  in DetectionService (drop initial replay so we don't redundantly
  clear+re-emit on first scan start).
- COMMERCIAL row label was clipping under the Switch. Shorten to
  'COMMERCIAL  •  Nest, Ring, Echo' (drop 'devices') and give every
  SourceToggle's Text Modifier.weight(1f) so labels wrap gracefully
  on narrow displays instead of getting truncated.
2026-05-07 23:12:17 -04:00
KaraZajac 0e4387df45 v0.2.1 — UI polish on the map widget + Settings
- Center the OVERWATCH header (was left-aligned with the gear pushed
  by SpaceBetween — now Box-aligned so the title sits dead-center
  regardless of icon width).
- Settings + drill-down: rename the MIC source label to "COMMERCIAL"
  ("Nest, Ring, Echo devices") for clarity. Internal enum stays MIC.
- Drop the GREEN/YELLOW/ORANGE/RED text inside the threat circle and
  bump the tier scrim alpha (0.40-0.65 vs. 0.18-0.50) so the color
  reads at a glance over OSM tiles.
- Force the per-event "Open in Maps" pin to use Google Maps instead
  of whichever geo: handler the user has set as default (Waze, etc.
  could intercept). setPackage("com.google.android.apps.maps") + a
  matching <queries> entry in the manifest so it works on Android
  11+; web fallback if Maps isn't installed.
- Add a blue user-position dot at the center of the map circle, drawn
  on top of any ALPR pins.
- Auto-fit the visible map radius to max(deflockProximityM,
  citizenProximityM) via zoomToBoundingBox so the circle's edge
  literally represents the alert distance the user has chosen.
2026-05-07 23:02:18 -04:00
KaraZajac 245055d9d2 v0.2.0 — live map circle + MIC detection (Echo/Ring/Nest/hidden cams)
- Replace the static threat circle with an osmdroid-backed map
  centered on the user, with red ALPR pins and a tier-color scrim.
  Falls back to the muted gradient when idle or before the first
  location fix arrives.
- Add DetectionSource.MIC: BLE/WiFi candidate path for Amazon
  Echo/Ring (Lab126 OUIs + AVS service UUID 0xFE03), Google Nest/
  Home/Chromecast (Google OUIs + mfg id 0x00E0), and generic
  Chinese hidden-cam vendors. Score capped at 84 (ORANGE) so RED
  stays reserved for ALPR/Axon-grade evidence. Toggleable in
  Settings; piggybacks on the BLE+WiFi scanners — no new radio.
- Drop the "[DЯΣΛMMΛKΣЯ]" stylized branding for a clean OVERWATCH
  header (notification channel + app label updated to match).
- Fix DeFlock geo-pin tap doing nothing: resolveActivity returns
  null on Android 11+ without a <queries> entry even when Maps is
  installed. Drop the pre-check, try/catch ActivityNotFoundException,
  fall back to a maps.google.com URL if no geo: handler exists.
2026-05-07 22:45:33 -04:00
KaraZajac e277c48e89 README: bring up to date with v0.1.7
Stale items corrected:
- Architecture file list referenced WazeClient.kt and WazeScanner.kt
  (deleted) and CDN-tile DeflockClient (now Overpass POST). Added the
  missing CitizenClient/CitizenScanner/SourceHealth/ThreatLevel files.
- Permissions table said "DeFlock CDN + Waze API" — now Overpass +
  Citizen. Added VIBRATE row.
- Settings section listed Waze instead of Citizen; missing the new
  Vibrate-on-escalation toggle and Restart-to-apply button.
- Status said "Phases 1-5 complete as of v0.1.0" — bumped to v0.1.7
  with a per-version changelog of what landed.

Added:
- Hero paragraph mentions notification + vibration alerting.
- New "How alerts work" section explaining notification updates,
  vibration cadence, drill-down sheet, and Open-in-Maps.
- Idle-visual note in scoring section.
- START_NOT_STICKY note in architecture.
- Open-app-settings recovery note in permissions section.
2026-04-28 22:32:16 -04:00
KaraZajac dc2eb9881e v0.1.7 — Settings back button returns to main, doesn't exit app
The screen enum lives entirely inside Compose, so the system back press
went straight to Activity.finish(). Added a BackHandler in the SETTINGS
branch that intercepts and routes back to MAIN.

versionCode 7 → 8, versionName 0.1.6 → 0.1.7.
2026-04-28 22:16:09 -04:00
KaraZajac 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
KaraZajac 74f26439fc v0.1.5 — remove Waze entirely
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.
2026-04-28 21:56:27 -04:00
KaraZajac 5a7a9e90e4 v0.1.4 — Citizen.com as 5th detection source
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.
2026-04-28 21:48:54 -04:00
KaraZajac 00584f58c9 v0.1.3 — DeFlock via Overpass + per-source health UI
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.
2026-04-28 21:36:47 -04:00
KaraZajac 6c57297f58 v0.1.2 — audit fixes
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.
2026-04-28 21:22:20 -04:00
KaraZajac 88e6f52ce7 Fix START button being disabled before permissions are granted
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.
2026-04-28 21:15:42 -04:00
KaraZajac 3574970a5f Add OVERWATCH v0.1.0 — full detection engine + polish
Phase 1 (BLE), Phase 2 (WiFi BSSID/SSID), Phase 3 (DeFlock map proximity),
Phase 4 (Waze live POLICE alerts), and Phase 5 polish all wired through one
DetectionStore. Confidence engine scores 0-100; UI maps to 4-tier circle.

Polish:
  - Stylized two-line app title: [DЯΣΛMMΛKΣЯ] // 0VΣЯW4TCH
  - Modal bottom sheet for source drill-down (tap circle)
  - Settings screen: per-source toggles, proximity sliders, theme select
  - SharedPreferences-backed Settings with StateFlow exposure
  - DetectionService respects per-source toggles at start time
  - Scanners read proximity overrides via supplier lambdas

README documents all sources, architecture, build steps, permissions, and
the legal disclaimer.
2026-04-28 21:10:57 -04:00
KaraZajac 1e195605df Initial commit 2026-04-28 19:21:53 -04:00