Inspiration
A grandson posted to r/fireTV on 2 September 2026 about his 92-year-old grandmother, whose voice control had stopped changing channels overnight: "After more of an hour working on it I had to stop before I broke her TV over my knee."
We coded 21,417 recent posts across thirteen subreddits. The theme we called "the ten-foot interface failing someone" matched 155 of them, more than broken captions, hard-to-hear dialogue and discovery fatigue put together, and 89 sat inside 4,013 r/fireTV posts, the biggest single theme there. CDC's 2022 BRFSS data weights US adult vision disability at 14,434,811 people and hearing disability at 17,692,113, and NIDCD reports that among adults 70 and older who could benefit from a hearing aid, fewer than one in three has ever used one. The fix has to live in the television, because the other device is in a drawer.
What it does
Simple Mode is one screen with eight enormous targets and a button that comes back to it from anywhere the television has wandered, built for a viewer we call Norah rather than an abstract persona. It reads the accessibility settings the household has already declared and resizes itself, with no setup wizard. If the grid is not enough, "Tell Me" takes a sentence in Norah's own words and acts on it. Behind a four-digit PIN, the family member who bought the stick can reorder, remove and add tiles from the television itself.
How we built it
Kotlin and Jetpack Compose, minSdk and targetSdk 28, because Fire OS 16 is Android 9 at API level 28. The adaptation is Fire OS's own documented surface, not a mock: CaptioningManager.isEnabled() with a CaptioningChangeListener, and the accessibility_audio_descriptions_enabled secure setting with a ContentObserver. Turn captions on in Fire TV's own Settings while the app runs and the type, the spacing and the focus border all grow, with no interaction with Simple Mode at all.
The home screen carries no photograph. An earlier build put stock hero art behind the wordmark on every screen but the one a judge opens first; it is gone now, replaced by a flat warm charcoal band closed by the same fern-green rule the inner screens already used, so the app's one real signature now runs across all of them. Type is a vendored Atkinson Hyperlegible rather than stock Roboto, a face drawn for low vision, and because it ships only two weights, hierarchy here is carried by size and colour alone rather than by weight. Behind the PIN, the caregiver's catalogue editor now spells its row controls out in words rather than arrow glyphs — "Move up", "Move down", "Remove" — beside the tile's position number, because that number is the one thing that makes "move up" mean something before it is pressed.
"Tell Me" is the one place a model changes the outcome, so it is the only place one appears. The app posts the utterance and the current tile list to a small standard-library Python proxy, which calls Amazon Bedrock's Converse API in us-east-1 over a model preference chain, us.anthropic.claude-sonnet-4-6 then us.anthropic.claude-sonnet-4-5-20250929-v1:0. Shipping an AWS credential inside an APK is a real defect, so the Android side only ever sees an HTTP boundary. What makes this more than a text call is what constrains it: a 15 second timeout, a drop to an offline keyword matcher on any failure, and a grid that is reachable identically whether Bedrock, the proxy or the household's network exist. The offline path was tested first rather than last, and the unit test for the timeout branch triggers a real TimeoutCancellationException from a real withTimeout, because the class is public and its constructor is not.
The ten-foot work is arithmetic, not taste. Fire TV is always density 320 on a 960 by 540dp canvas, so the scale runs four steps, 28 / 38 / 50 / 66sp, with 28 as the floor rather than the middle of a six-step ladder inherited from a shared template; the focus ring is 8dp with a 1.08x scale and a brighter fill, three redundant channels animating in 120ms against a D-pad key repeat of about 50ms; rows are focusGroup()s; every screen requests initial focus deliberately. "Live TV" and "Norah's Shows" are sample content hosted inside the app, so the demo never depends on what a judge happens to have installed; Netflix, Prime Video and YouTube launch by real package name and fail to a plain "not set up on this TV yet" screen. 108 unit tests, and every interaction was driven by adb shell input keyevent, never a click, because a click hides the focus failures a remote exposes.
Challenges we ran into
Fire TV documents no launch-on-boot or launcher-replacement API, and /docs/fire-tv/launch-on-boot.html returns 404. "One button always gets you back here" had to rest on something we could stand behind, so the mechanism is a TYPE_APPLICATION_OVERLAY window drawn by a foreground service under the ordinary SYSTEM_ALERT_WINDOW grant. It draws on top of everything, including protected video, because it never touches that app's window.
That window is deliberately FLAG_NOT_FOCUSABLE, on purpose: an overlay that could take focus would steal the remote from whatever the viewer is actually watching. The same flag means a D-pad press can never reach it at all — dumpsys window shows focus staying with the app underneath the whole time it is on screen. The button answers a touch rather than a D-pad press, which is exactly why the FLAG_NOT_FOCUSABLE window needed a working touch path at all, and that path is confirmed: built for Fire OS and tested on a real Fire TV, the button is reachable and returns the viewer to Simple Mode there, not just on the emulator that stood in for it during development.
That overlay then failed silently. Gravity.END moves a window further past the anchored edge rather than inward, so a positive x inset pushed the button clean off the display, while dumpsys window windows reported it visible the whole time. A screenshot found it; nothing else would have. Separately, a force-stop killed the service while the home screen still showed the toggle as on, because the state had been seeded from whether the permission was granted rather than whether the service was running.
The brief named anthropic.claude-sonnet-5 as verified. It is in ListFoundationModels and returns AccessDeniedException on Converse. We found that by invoking it before writing code against it, which is the only reason the feature did not silently fall back to keywords with the demo looking identical either way.
Accomplishments that we're proud of
The adaptation is real, unmocked, and demonstrable in fifteen unbroken seconds from the TV's own Settings screen. The grid works identically with Bedrock, the proxy and the household's network all absent, which is the correct property for a product whose user cannot work a television. The caregiver catalogue editor runs on the television, needing no backend, no account and no pairing flow.
What we learned
A screenshot taken after a press proves focus can move. It does not prove a screen is usable from a cold, untouched remote, and a cold launch with nothing focused reads as a dead remote. And ListFoundationModels answers "does this account know this model exists", not "can this account call it".
What's next
Running under VoiceView on real Fire TV hardware, the com.amazon.accessibility node extras Compose does not expose, an in-app text size control since Fire TV ships no system one, and a Vega OS build.
Built With
- android
- fire-os
- jetpack-compose
- kotlin
Log in or sign up for Devpost to join the conversation.