dumbtv
dumbtv takes the ads, screen tracking and update nags out of your Fire TV over ADB. No root, none of your apps touched, every step can be undone. Free and open source.
Android navigation that avoids the fields of view of mapped ALPR cameras — the 139,391 of the 142,257 in the 50 states and DC whose facing is recorded — routed offline, so your start and destination s
SecurityJava · KotlinGitHub @KaraZajacOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Navigation that routes around automated licence-plate readers — an offline Android app built on OsmAnd, and the exposure study behind it
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Latin: byways, side roads.
Navigation that routes around automated licence-plate readers.
Website · trames.karazajac.io — what it is, how it works, and the APK.
Download · latest release — debug-signed APK, arm64-v8a, Android 7.0+. Sideload; there is no Play Store build.Read the full README ↗Latin: byways, side roads.
Navigation that routes around automated licence-plate readers.
Website · trames.karazajac.io — what it is, how it works, and the APK. Download · latest release — debug-signed APK, arm64-v8a, Android 7.0+. Sideload; there is no Play Store build.
TRAMES answers an ordinary navigation request — an address, a route — with one additional constraint: prefer paths that no ALPR camera can actually see. Cameras are modelled as directional wedges, not circles, because a reader watching northbound traffic says nothing about the southbound carriageway, and treating it as a circle makes the router detour around roads nobody is being read on.
It does this offline: by default since v1.2.0, and only since v1.2.4. Routes are computed on the device against ALPR-tagged maps, so a start, a destination and a departure time never leave the phone: sending a server your itinerary in order to dodge cameras trades one movement record for another. The hosted online server earlier versions could use was retired on 2026-08-29, and v1.2.4 removed the app's online engine. v1.2.5 and v1.2.6 also shut off what the OsmAnd code underneath sent on its own — see What leaves the device.
| Path | What it is |
|---|---|
client/ | Android app — a fork of OsmAnd (GPLv3): offline ALPR avoidance for car/bike/foot, a camera map layer, and an in-app map downloader |
server/alpr, server/graphhopper | The routing server the study routes against — stock GraphHopper 11 plus a preprocessor that turns OSM ALPR data into camera-cone geometry. The app no longer uses one; the public instance was retired on 2026-08-29 |
server/offline/ | The offline pipeline — tags camera-watched ways, builds .obf maps that carry the tag, and packs camera positions for the map layer |
research/ | The measurement study: pipeline, results, and the paper |
We routed 56,133 real home–work commutes — drawn from Census LEHD LODES with probability proportional to the number of workers actually making each trip, in all 50 states and the District of Columbia — twice each: once normally, once avoiding the fields of view of every licence-plate reader we can place: the 143,721 mapped in OpenStreetMap in the 50 states and DC (snapshot of 2026-10-21), less 168 since taken down, and 42,694 more in service that only Flock Safety's own device registry records (as of December 2025, less those confirmed removed since) — 186,247 in all. National figures weight each state by its commuters. Then we did it again on OpenStreetMap's camera map as it stood on the first of every month since January 2024, rebuilt exactly from its edit history; the registry carries no dates, so the trend is the map's alone.
Paper: research/paper/trames.pdf — build with
tectonic -X compile research/paper/trames.tex.
Every camera source the study uses, resolved to one device list:
research/scripts/build_supermap.py --removals research/data/registry_removals_2026-10.json \
-o research/out/supermap/alpr_supermap.json \
--report research/out/analysis_supermap.txt --gzip # ~4 min cold, ~100 s cached; 527 MB (54 MB gzipped)
390,180 devices in the United States — 89,363 carried by both OpenStreetMap and Flock's own
device registry, 245,573 by the registry alone, 55,244 mapped but absent from it. 239,433 of them
are licence-plate readers, 142,549 with a surveyed bearing; 180,644 devices read plates and are
either in service or mapped by OpenStreetMap, which records no status. Each device carries where
it is, which sources say so and how far apart they put it, what it is (plate reader, video, audio
detector, speaker, drone, infrastructure), its status, its bearings where anyone surveyed them,
its operator and transparency portal where they are known, its tract and place, and the map
snapshot in which its node first appears. Around each device: the traffic on the road it
watches (HPMS AADT and functional class, for the 241,994 devices within 100 m of a counted
road), the OSM road class a mapped node stands on, the one police portal of the place it stands
in (109,085 devices; the likely operator of a municipal camera, not a recorded one), an operator
inferred from other devices on the same pole (7,326), the fleet number an agency gives it, and
flags for what a registry name says its status is, and for probable duplicates on either side.
Every device also lists the roads it watches: the drivable ways its cone crosses (the same
60 m, 45° sector the router uses, one per surveyed head), or, where no source records which
way a camera looks, the ways within a 60 m circle of it — the cone's length, in every
direction, so a camera of unknown bearing is taken to see as far as one with a bearing. A
first pass used 40 ft and left 13% of bearing-less plate readers touching no road, because
cameras stand set back from the carriageways they watch; at 60 m, 428 of 96,884 (0.4%) do.
Of the 5,990 bearing-less devices of any kind still touching no road, most are indoor and
campus video cameras that the registry places at one building's coordinate.
The study's merged map is drawn from it: the in-service plate readers it holds from the registry
alone, 42,694 in the 50 states and DC, each given a 60 m disc
(cones_from_supermap.py). Registry readers confirmed
taken down by 21 October 2026 (989, each with its source in
registry_removals_2026-10.json) carry the status
removed and stay out, with the mapped nodes that stand on them.
The registry's rotationAngle is not a bearing — its median
disagreement with a surveyed one is 90°, which is what unrelated angles would give — so it is
carried verbatim and never used as one. The report beside it counts everything, by state, class,
vendor and status; 's docstring is the
specification.
The app's router has a different problem from the study's: OsmAnd's router reads only what
is inside the .obf map, so the cameras have to be in the map.
server/offline/tag_ways.py intersects the same 60 m / 45° cones
against an OSM extract and writes alpr=yes onto each stretch of road, junction to
junction, that a cone touches, splitting a way where its watched and unwatched stretches
meet. Where a cut would change routes even with avoidance off, as on a bend, the tag runs
on to the next junction, so a little more road is tagged, never less. The tag holds in
both directions unless the carriageways are mapped as separate ways. Maps before v1.2.7 tagged the whole way as OpenStreetMap draws it, which can run
past several junctions: one cone tagged 2.9 km of the Dulles Toll Road..obf drops unknown tags unless they are
declared, so rendering_types.xml gains a <routing_type tag="alpr"> — the one
genuinely uncertain step, proven on a spike before the continent was built.routing.xml gets five mutually exclusive levels
(alpr_off … alpr_max) in the car, bicycle and pedestrian profiles, sharing
an alpr_avoidance group so OsmAnd renders them as one picker with no UI code.The multipliers are 0.3 / 0.1 / 0.05 / 0.01, default Maximum since v1.2.7 (Strong
before) — the numbers the study's routing server uses too — and since v1.2.7's maps the app
applies them to nearly the same stretches of road: those, junction to junction, that a cone
touches (step 1). So the app's default is the study's setting. It still does not have the
study's routes, because the app's router, speeds and turn costs are OsmAnd's; the study's
detours are not a measurement of the app's.
A camera with no recorded facing gets no cone, so the router does not avoid it: 2,032 of the 144,692 North American cameras in the 2026-10-21 snapshot (1,946 of the 143,721 in the 50 states and DC). The map layer draws them as plain pins, without a wedge, and the route readout counts one as seeing the route if the route passes within 60 m of it.
Three things worth knowing:
GeneralRouter returns the first matching priority
rule and stops; priorities do not accumulate. The ALPR rules are therefore first in
each block. Anywhere later, a watched road that also carried smoothness=bad or
access=destination would match that instead and silently escape avoidance.routing.xml is not ours and is overwritten on every build — it is synced from
upstream OsmAnd-resources and gitignored at both ends. trames-patch-resources.sh
keeps the delta in a tracked, idempotent script that the build applies and that fails
loudly if upstream moves. Without it the app builds clean and quietly avoids nothing,
which is the worst failure this project has: routing still works, it just stops doing
the one thing the app is for.Prebuilt maps for all 50 states + DC are hosted at maps.blackflagintel.com and download in-app, from TRAMES offline maps in the drawer; since v1.2.5 the app offers them on first start.
The app no longer routes online: the public instance of this server was retired on
2026-08-29, and v1.2.4 removed the app's online engine (see server/README.md).
It is the engine the study routes against.
GraphHopper resolves custom_areas.directory into a spatial index at graph import
time. Register the camera cones (160,230 on the 2026-10-21 map, merged into 138,046
polygons) as one area named alpr, and a per-request custom model can then reference it with
no geometry in the request at all:
{ "priority": [ { "if": "in_alpr", "multiply_by": 0.01 } ] }
That is the whole trick, and it is why this needs no forked routing engine. Avoidance strength stays a continuous per-request knob. The cost is that regenerating cones requires a full re-import.
Two consequences worth knowing before changing the config:
car_alpr borrows car's landmark preparation. That halves preparation time and is
valid only because avoidance multiplies priority strictly downward, so the borrowing
profile's weights never exceed the preparation profile's on any edge.Building the app needs only steps 0 and 1 — the graph is for the study's routing server, which the app does not use.
# 0. third-party OsmAnd assets the client build reads (~70 MB)
./setup-resources.sh
# 1. build the app (applies trames-patch-resources.sh first — see below)
cd client && ./trames-build.sh
Optional, to run the study's routing server yourself:
# camera geometry (Overpass -> cones)
cd server/alpr && ./fetch-na-cones.sh
# import the graph, then serve (1.5–2 h for North America with cones)
cd ../graphhopper && ./rebuild-with-cones.sh
Optional, to build your own ALPR-tagged offline maps instead of using the hosted ones:
cd server/offline
python3 cones_from_cameras.py --cameras ../cameras/cameras.json -o .work/us-alpr.geojson
python3 build_maps.py --mapcreator /path/to/OsmAndMapCreator --states delaware
python3 build_camera_pack.py -o cameras-us.json.gz # positions for the map layer
A full-map state build is memory-hungry: California and Texas each need roughly a 48 GB JVM heap and a couple of hours.
--roads-onlyis far cheaper if you only need routing and not map display.build_maps.pyis resumable and skips states already built.
resources/The client build copies map styles, routing profiles, POI types, icons, fonts and
voice prompts from ../../resources, a checkout of
OsmAnd-resources. It is not tracked
here: it is 576 MB of third-party assets we neither maintain nor usefully diff, and
vendoring it would quadruple the size of this repository.
setup-resources.sh fetches it instead. The build reads roughly a tenth of that tree, so
the script uses a blobless partial clone plus a sparse checkout and pulls only the paths
the Gradle files actually reference — ~70 MB rather than 576 MB.
./setup-resources.sh # what the app build needs (~70 MB)
./setup-resources.sh --with-tests # plus upstream test fixtures
./setup-resources.sh --full # the entire upstream repo (~576 MB)
./setup-resources.sh --update # refresh an existing checkout
Do not build without it. Several of the consuming Gradle tasks are
Synctasks, which make the destination match the source. With the source missing they do not fail — they empty the destination, and the build then succeeds and produces an app with no rendering styles, no routing profiles and no fonts.setup-resources.shverifies its own output for exactly this reason.
If a build ever fails on a missing asset, the path list in the script came from:
grep -rn '\.\./\.\./resources/' --include='*.gradle' client/
The research pipeline is independent of the app:
cd research
./data/fetch.sh all # LODES + tract attributes, 50 states and DC
../server/.venv/bin/python scripts/build_sample.py --states all --per-state 1200 \
--frame out/sampling_frame.json --draws out/sample_draws.csv -o out/commutes.csv
./scripts/run-full.sh # hours, detached, resumable
./scripts/run-history.sh # the same commutes against earlier camera maps
# (built in server/alpr — see "Past-date maps" there)
./scripts/run-analyses.sh # weighted analysis, vendor, cone radius, trend (the map alone)
./scripts/run-merged.sh # the merged map: the map's wedges plus a 60 m disc for each
# registry reader it lacks, in a graph of its own; the
# commutes it exposes routed again (needs the device map above)
./scripts/run-merged-analyses.sh # merged headline, paired comparison with the map alone,
# directed scoring, the figures no other report prints
# (paper_numbers.py), then the paper's figures and tables
Your trips don't. Routes are computed on the device, and the map draws cameras from a downloaded pack. Since v1.2.6 the app sends nothing until you tap something; what the phone itself does when an app asks for location is at the end.
TRAMES's own code makes three kinds of request, all plain downloads from
maps.blackflagintel.com that follow from a tap, and none carries coordinates: the map
list (manifest.json), a state's map file — which tells the server which state you chose —
and the camera pack, fetched again with every map download. The host sits behind
Cloudflare, so Cloudflare sees what the server sees: your IP address and the files you fetch.
What the OsmAnd code underneath sent on its own, until v1.2.5. On first start OsmAnd's
welcome screen asked osmand.net/api/geo-ip where the phone was, fetched OsmAnd's map index
and offered OsmAnd's standard map of that region, which carries no cameras; OsmAnd also
downloaded its help index from osmand.net. On about one start in five it fetched the map
index from download.osmand.net to look for map updates. At zooms 9 to 11, wherever no
installed map covered the view — before a TRAMES map is installed, everywhere — it fetched
the index again to show a banner offering its own map. On phones with Google Play it checked
subscriptions with osmand.net daily, then asked for sale messages. None of these carried
coordinates. Each showed OsmAnd's servers the phone's IP address, and most carried a random
install ID that OsmAnd renews every three months. v1.2.5 turns them all off: the install ID,
the welcome screen and the banner in TramesDefaults, the rest in TramesPrivacy.
What sent coordinates once tapped, until v1.2.5. Calculate online, offered when a route
needs a map you don't have, sent the start (or the last known location), every stop and the
destination to maptile.osmand.net. Online photos, a row in a place's menu, sent that
place's coordinates and your last known location to osmand.net, and once opened it stayed
open for every place after. Nearby places, a card in search, shared that row's saved open
state, so opening it opened the photos everywhere. v1.2.5 removes all three.
What still can, if you open it. Apart from what the phone does below, nothing else goes out on its own, but OsmAnd's features still talk to their servers when you use them:
download.osmand.net the region you pick, with the days since install and the number of
starts. Their places, and those in OsmAnd's Wikipedia maps, carry photos that search
results and the Wikipedia layer load from data.osmand.net, naming the places you look
at. TRAMES maps carry no such photos — one more reason to keep to them.tile.osmand.net by default), which is the area you are looking at.openstreetmap.org.osmand.net.nominatim.openstreetmap.org; Send on an empty result sends the query and your location
to osmand.net.example.com.osmand.net.osmand.net. If you agree, it uploads the events it has logged since install, including
the names of maps you downloaded.What your phone does when TRAMES asks it. Location comes through Google Play services where they are installed; with the phone's Google Location Accuracy on, Android sends Wi-Fi and cell scans to Google for any app that asks for location. When TRAMES opens, at most every 16 hours, OsmAnd asks the GPS to refresh its assistance data, which the phone fetches from its chip vendor's or Google's servers. And spoken directions come from the phone's text-to-speech engine, which may synthesize them online.
What the phone did until v1.2.6. With the phone's backup on, Android copied TRAMES's
settings, including the last map position and the navigation destination, along with the
favourites backup and the map-marker and OSM-note databases, to the phone's backup account
and onto a new phone. v1.2.6 turns OsmAnd's backup off once (Settings → OsmAnd settings →
Auto-backup turns it back on), so nothing more is copied — but it deletes nothing: a copy made
before stays in the backup account until you remove it. With backup off the app also takes
nothing back, so reinstalling v1.2.6 or later restores none of it; installing an earlier
version would. To remove it, delete the phone's backup in Google Drive, which takes
every app's with it, or run adb shell bmgr wipe <transport> org.soulstone.trames, taking
the transport from adb shell bmgr list transports. And from 15 days or 100 starts after
install, on a phone with the Play Store, OsmAnd's rate-us check asked Google Play for its
in-app review screen without a tap; v1.2.6 removes it.
Earlier versions sent more. Until v1.2.4 the app had an online routing engine, which sends a server your start and destination, and the camera layer could ask for the cameras in a box around the current view, which is to say your location — permitted only while a profile routed online, and, until v1.2.3, on a fresh install before you had opted into anything. v1.2.4 removed both.
The routing server has no authentication and no rate limiting. It binds to localhost and must not be exposed directly to the internet. Any public deployment needs a reverse proxy supplying both.
Released APKs are signed with the public Android debug key. Anyone can build an APK signed with the identical key and Android will accept it as an update, so the signature proves nothing about who built it. Verify the SHA-256 published on the release, or build from source.
The parts carry different licences; LICENSE maps them:
client/, the app, derives from OsmAnd and is GPLv3, under OsmAnd's notice in
client/LICENSE. Its artwork is CC-BY-NC-ND 4.0 (below).server/ and research/, the map pipeline, the study's routing server and the
study, are TRAMES's own code under the MIT licence.Upstream history is not carried here: the project does not track OsmAnd continuously, and
the fork cost 1.17 GB to hold five commits of TRAMES work. client/ started as the
upstream tree at 99f04deacd with those five commits replayed; every TRAMES change since is
an ordinary commit here (git log -- client). OsmAnd's LICENSE, AUTHORS.md and all
per-file copyright headers are preserved verbatim. See
client/TRAMES-NOTICE.md.
To diff against or re-sync with upstream:
git remote add osmand https://github.com/osmandapp/OsmAnd.git
OsmAnd's artwork is CC-BY-NC-ND 4.0, not GPLv3. It may not be modified in a derivative work — it has to be replaced. This is a live obligation for anyone rebranding the client, not a footnote.
Camera data comes from OpenStreetMap (ODbL), fetched with the Overpass API when the maps and the camera pack are built — the same corpus surfaced by DeFlock. It is contributed by volunteers and is incomplete in ways that are not random — the central caveat of the study.
The study also reads Flock Safety's device registry as it stood in December 2025, from the
map an independent researcher published at flocksurveillance.org,
less the 989 in-service readers confirmed taken down by 21 October 2026 (each listed with its
source in research/data/registry_removals_2026-10.json).
That data is not redistributed here: the device list and the registry's discs are built
locally and gitignored, and the app does not carry them.
build_supermap.py