12 KiB
Contributing
Thanks for helping more TVs lose their ads. Three kinds of contributions are especially welcome.
Working with a coding agent? Point it at AGENTS.md: the same rules, written for agents.
The reasons a pull request most often came back for changes or was closed, one example for each, are
in docs/DOS-AND-DONTS.md. It is the short list of mistakes to skip, and it
does not replace the sections below.
Where does my idea go?
The launcher stays small on purpose, so pick the smallest home that fits:
| Your idea | Where it goes |
|---|---|
| A TV model, a fix, a translation, a small switch that is off by default | A pull request here (sections 1 to 3 below); a new switch with a screenshot in docs/switches.md |
| A new look: colours, wallpaper, dock, columns | themes/, with a screenshot from a real TV |
| A widget or tile that can be its own small app | addons/, as a module card with a screenshot from a real TV; it never touches launcher/ |
| A whole new launcher, a heavy restyle, a port to another platform | Your own repo, plus one row in SHOWCASE.md (section 4) |
Everything merged into themes/ and addons/ shows up on cleanyour.tv/addons.
1. Add your TV model
Every brand ships different bloat. If you ran the prompt (or did it by hand) on a TV that isn't in
examples/, add it. The same steps for a coding agent are in
docs/workflows/tv-list.md.
- Create
examples/<brand>-<model>/debloat.txt. Copy the format fromexamples/xiaomi-mi-tv-4a/debloat.txt: one package per line, grouped with a comment that says what the group is, and a trailing comment on anything non-obvious ("re-enable for firmware updates", "needed for HDMI-CEC", etc.). - Put the exact model name, Android version and firmware build in the header so people can tell
whether it matches theirs.
tools/tv.py --probeprints all three. - Only list packages you have actually disabled and lived with for a few days. Note anything that broke and what you re-enabled.
- Add
examples/<brand>-<model>/tv.json, the row the TV tables show for your list. Copyexamples/xiaomi-mi-tv-4a/tv.json:tv(the model as the tables name it),os,brand(spelled as in the README's brand grid; a new brand is fine) andnotes(one line: what the list disables and keeps). A package that line names in backticks withkept,disabledorrefusedstraight after it is a claim, anddocs/workflows/tv-list.mdstep 4 has what the tests read it against. One optional key,readme_os, is the OS cell of the short table inREADME.nl.mdwhen it should read shorter thanosdoes inexamples/README.md. Change nothing outside your folder: the TV table inexamples/README.md, the brand grid in the READMEs and the counts are generated from every folder'stv.jsonafter merging, so a PR that edits them only collides with the other open lists. - If your TV's Live TV app or stock launcher has a different package name, say so in the header. Where
a script needs the stock launcher,
tools/restore.shtakes it as its second argument (tools/restore.sh <list> <package>) andlauncher/build-and-install.shtakes it in the environment, asSTOCK_LAUNCHER=<package>(-StockLauncheron the PowerShell twin, andnonekeeps it enabled) - not as an argument. The launcher shows your Live TV app in its Inputs menu fromlive_tv_package, inconfig.xmlor a theme.
Never list packages to uninstall. Everything in this repo disables with pm disable-user, which
is reversible from the TV's own settings if the tools are gone.
Android's own packages are not bloat either. Never list the permission controller, the package
installer, ext services or ext shared, module metadata, a com.google.android.overlay.modules*
overlay, systemui, shell, phone, settings, tv.settings, android, or a com.android.providers.*
package other than calendar, contacts, user dictionary and blocked numbers. A TV that loses one of
them may never boot again (#884, and
what to do if it already happened), so
tools/disable.sh, disable.ps1 and check.py --fix refuse such a list outright, with no switch to
override it, and a pull request that names them comes back for a rework.
2. Improve the launcher
The launcher lives in launcher/ and is deliberately small: plain Java, AndroidX RecyclerView and
DynamicAnimation, no Kotlin, no Compose, no network. It targets TVs with 1 GB of RAM. A new switch follows
docs/workflows/launcher-switch.md, a new language
docs/workflows/translation.md, and a speed-up or slow-down is measured with
docs/workflows/perf-ab.md. In every change:
- No blur, no video backgrounds, no polling, no background services.
- Anything that depends on a specific TV goes in
app/src/main/res/values/config.xml, not in code. - Keep D-pad navigation deterministic. Test Up/Down/Left/Right from every edge.
- Verify on a real TV, not only the emulator. The easiest way is remote key injection plus a
screenshot:
tools/tv.py --screenshot shots/after.png DPAD_DOWNpresses the key, waits a second (--waitchanges that), captures on the TV and pulls the PNG, creating the folder if it is missing. Include a screenshot in your pull request. - Long presses (hold OK on Settings or on a tile) can't always be injected:
input keyevent --longpressarrives as a short press on some firmware (Android TV 8.0 on a Philips opens the app instead). Writing to the remote's own input device works, because Android then repeats the held key itself. Find the device and the OK key withtools/tv.py 'getevent -pl'(a remote that listsKEY_OKis scancode 352), then hold it for a second:tools/tv.py 'e=/dev/input/event1; sendevent $e 1 352 1; sendevent $e 0 0 0; sleep 1.2; sendevent $e 1 352 0; sendevent $e 0 0 0'. This needs the shell user in theinputgroup (tools/tv.py id). SELinux can refuse it even then: on a TCL 55C728 (Google TV 11, enforcing) the shell is ininput, yetsendeventfails withPermission deniedandlogcatshowsavc: denied { write } ... scontext=u:r:shell:s0 tcontext=u:object_r:input_device:s0, while--longpressarrives as a short press too. On a set like that, test long presses with the remote.
Automated navigation and lifecycle checks
- Use D-pad key events throughout focus tests, including entering and leaving Reorder. Injected touch input can enter Android's system-wide touch mode, where ordinary buttons may not take focus. If a test uses touch, send a directional key and verify the focused tile before continuing with D-pad assertions.
- After Home, an app transition or a reboot, wait with a timeout for the launcher UI and focused window before sending navigation or capturing a hierarchy. A fixed sleep or a boot-completion property alone does not establish that Home is ready. Android's UI Automator guidance demonstrates waiting for the launcher and for specific UI conditions.
- For synthetic memory checks, record the process state, requested trim level and command result. ActivityManager can reject background trim levels for foreground processes. To exercise hidden Home, put an opaque app in front and verify Home's process state; use a trim level supported by the target Android version. Treat a rejected command as a test setup failure. A delivered trim callback tests recovery behavior, not performance under real memory pressure.
Build with TV_HOST=<ip> launcher/build-and-install.sh (JDK 17, Android SDK platform 34 and
build-tools 34.0.0; on macOS brew install openjdk@17 android-commandlinetools then
sdkmanager "platforms;android-34" "build-tools;34.0.0"; with the newer command-line tools the form is
android sdk install platforms/android-34 build-tools/34.0.0).
No TV on the network, or an APK for someone else to install? Build it on its own: from launcher/,
./gradlew --no-daemon assembleRelease leaves it at
launcher/app/build/outputs/apk/release/app-release.apk, which has to stay under 589,824 B (576 KB);
CI builds with the same command and refuses a bigger one. Gradle also has to find the SDK, and this is
where a hand build stops: launcher/build-and-install.sh writes the gitignored
launcher/local.properties for you, so by hand either run
ANDROID_HOME=~/Android/Sdk ./gradlew --no-daemon assembleRelease or put sdk.dir=<sdk> in that file;
without one or the other Gradle stops at SDK location not found. The JDK has to be a full 17 - a JRE
answers java -version as a 17 but has no jlink, which the Android Gradle plugin needs (#1255) - and
Homebrew's openjdk@17 is keg-only, so point JAVA_HOME at it
(export JAVA_HOME=$(brew --prefix openjdk@17)).
With neither a JDK nor an SDK on this computer, build it in a container that has both:
tools/docker-build.sh launcher (Docker only; addons/<name> builds an add-on, and the Gradle task
defaults to assembleRelease). It writes the APK to the same place, so
SKIP_BUILD=1 TV_HOST=<ip> launcher/build-and-install.sh installs it. Its debug key lives in the
cleanyourtv-gradle volume rather than ~/.android, so installing over an APK built here fails with
INSTALL_FAILED_UPDATE_INCOMPATIBLE unless both use the same key (HOST_KEY_DIR=~/.android), and
tools/docker-build.md has the rest.
Ideas that fit the direction: an idle screensaver with clock and ambient colour, per-TV icon packs for apps without banners, a settings screen to edit the dock from the TV, localisation. Ideas that don't: recommendation rows, content feeds, ads, telemetry.
A new theme goes in themes/<name>/ with its config.json and a required screenshot.jpg of Home on a
real TV with that theme; see Adding a theme.
3. Improve the prompt and tools
PROMPT.md is what most people will actually use. If your assistant got stuck somewhere, the fix
usually belongs in the prompt as one extra sentence, not in a script. Keep it brand-neutral; put
brand-specific knowledge in examples/.
tools/tv.py and tools/b64push.py exist because the stock adb is blocked on recent macOS. If
you find a cleaner workaround, open an issue first.
4. Show what you built
Built your own launcher, a port to another platform, or an add-on that is too big or too personal
for the shared launcher? Put it in its own repository and add one row to
SHOWCASE.md. That page is the place for alternative launchers, forks with a
different look, and tools around the launcher; this repo keeps one small reference launcher and
points at everything else. The rules for a row are on that page.
Things that stay in this repo: debloat lists, fixes and small, opt-in features for the reference
launcher, prompt and tool improvements. A feature that can be its own small app goes in addons/
as a module card. Things that go in the showcase: a second launcher, a
restyle, a platform port, anything with its own background services or network access.
Pull requests
- One topic per PR. Small is fine.
- Read
docs/DOS-AND-DONTS.mdbefore you open one: it is why most PRs come back for a change, with an example for each reason. - Say which TV you tested on.
- No new dependencies in the launcher without discussing it in an issue.
- Be kind in reviews. Most contributors here are doing this for their parents.
By contributing you agree your work is released under the repo's MIT licence.