GlyphMaps
Turn-by-turn navigation on the back of a phone. GlyphMaps mirrors Google Maps' next maneuver onto the Nothing Phone (4a) Pro's 137-LED Glyph Matrix, so a glance at a face-down phone shows your next turn.
- 137
- LEDs
- 12
- maneuvers
- 2.2 MB
- APK
The idea
My phone has 137 LEDs on its back, and for months they did nothing but blink at notifications. Meanwhile every drive meant glancing at a bright six-inch screen for what amounts to one arrow and one number. The Nothing Phone (4a) Pro's Glyph Matrix is a circular 13x13 dot grid, which happens to be exactly the right shape for a turn arrow. So: flip the phone face-down on the dash, and the next turn lights up on the back. The screen is for routing. The back is for the glance.
The API that said no
Nothing's official way onto the Matrix is the Glyph Toy framework, and it's throttled to always-on-display cadence: one update a minute. Navigation needs one every couple of seconds. Dead end, by design. But the way in was hiding in plain sight: setAppMatrixFrame, the SDK's raw framebuffer call, isn't throttled at all. It just needs a foreground lifecycle to stay alive. So GlyphMaps runs as a foreground service that claims the Matrix when you start navigating and releases it the moment the route ends, with a 20-second watchdog so your usual Glyph toy always comes back. That one architectural choice is why the app exists.
Reverse-engineering the turn
Google Maps has no public turn-by-turn API. The only surface is its live navigation notification, so I logged real captures, diffed them across maneuvers, and reverse-engineered the format. A listener scoped to exactly the Maps package and the navigation category parses out the maneuver and distance, and a turn hits the back of the phone within a few hundred milliseconds of Maps announcing it.
Google's routing vocabulary has over 60 maneuver constants. On a 13x13 grid most of those distinctions are invisible, so they collapse into 12 shapes you can read at arm's length: chevrons, corners, forks, a hooked U-turn, a ringed roundabout, an arrival flag. Precedence matters here (sharp-left has to win over turn-left), and the post-trip 'How was your route?' survey gets dropped at the door.
One pure function
Everything renders through a single pure composer: parsed state in, 13x13 brightness grid out. Arrow on top, distance scrolling underneath as a marquee, because the grid is 13 LEDs wide and '1.5 km' isn't. The same function drives the LEDs and the in-app preview, so what the screen shows and what the back lights are pixel-identical. The arrows themselves are authored as ASCII strings, X for the bright head, o for the dim tail. The whole vocabulary is readable in the source.
The sweep that cannot drift
The animated mode originally used hand-drawn frames, one set per arrow. They drifted: the animated LEFT pointed at a different column than the static LEFT. Two sources of truth, both wrong. I deleted every hand-authored frame, and now the sweep is generated procedurally from the static pattern, so a settled animation frame lights exactly the same cells at exactly the same brightness. Drift isn't fixed. It's impossible.
Private by construction
An app that reads your navigation notifications had better be provably harmless: 100% on-device, no network code, no analytics, no account. A pre-release privacy audit still caught something real, though. The dev capture log could leak street names to logcat, so every code path that touches notification content is now gated behind a dev-only build flag, and the release build strips logging entirely.
Shipped
v1.0.0 runs on my actual phone on actual drives: a signed, R8-minified 2.3 MB APK on GitHub Releases. Twelve arrows, twelve generated sweeps, brightness sliders, two display modes. AGPL-3.0, after a deliberate MIT-to-AGPL migration with a full history scrub, so no one can quietly take it closed.