7 min read

Debloating a Google TV Streamer with AI: Apple TV Look via Claude Code

Der Anlass für dieses Projekt war heute Mittag ein Video vom YouTube-Kanal OwnGalaxy über cleanyourtv. Das ist ein Open-Source-Repo, das Android-TV-Geräte entrümpelt und ihnen einen schlichten Launcher im Stil des Apple TV verpasst. Die Idee dahinter: Man gibt einem KI-Agenten mit Shell-Zugriff einen fertigen Prompt, und der erledigt den Rest per ADB direkt auf dem Fernseher.

Mein Google TV Streamer lief bisher mit Projectivy als Launcher und war schon halbwegs aufgeräumt. Trotzdem liefen im Hintergrund weiter Telemetrie, Werbedienste und vorinstallierte Apps, die niemand braucht. Also habe ich Claude Code darauf losgelassen. Aus „mal kurz debloaten" wurde ein Abend mit zwei selbst geschriebenen Apps. Darum geht es in diesem Artikel.

Voraussetzung: ADB über das Netzwerk

ADB (Android Debug Bridge) ist die Entwicklerschnittstelle von Android. Darüber lassen sich Shell-Befehle ausführen, Apps installieren und Screenshots ziehen. Auf dem Fernseher reichen dafür drei Schritte:

  1. Einstellungen → Geräteeinstellungen → Info → siebenmal auf „Build" klicken
  2. In den Entwickleroptionen USB-Debugging einschalten
  3. Beim ersten Verbinden den Dialog „USB-Debugging zulassen?" bestätigen

Die IP musste ich Claude nicht einmal nennen. Es hat das Gerät per mDNS gefunden, weil der Streamer sich als _androidtvremote2._tcp und _googlecast._tcp im Netz meldet. Als ADB-Client dient das Python-Paket adb-shell in einem venv, das im Repo als tools/tv.py verpackt ist. Das umgeht nebenbei die macOS-Eigenheit, dass die Local-Network-Privacy das normale adb gern blockiert.

Stufe 1: Backup und Debloat

Vor der ersten Änderung zieht der Agent ein vollständiges Backup: Paketliste, deaktivierte Pakete, getprop und alle drei Settings-Namespaces. Danach wird nur deaktiviert und nie deinstalliert:

pm disable-user --user 0 com.google.android.feedback

Das Repo bringt dafür tools/disable.sh mit, und das hat ein paar sehr sinnvolle Schutzmechanismen. Core-Pakete wie Permission Controller oder Package Installer verweigert es grundsätzlich. Bei mir hat es außerdem eine App abgelehnt, über die der Streamer seine Einstellungen öffnet, weil ich mich sonst aus den Entwickleroptionen ausgesperrt hätte. Gut, dass das Skript schlauer war als mein Plan.

Deaktiviert habe ich in mehreren kleinen Batches, mit einem Screenshot nach jedem Schritt:

  • Telemetrie: Feedback, Feedback-Consent, Logging der Fernbedienung
  • Werbung und Federated Learning: Privacy Sandbox, AdServices, On-Device-Personalization
  • Vorinstalliertes: Play Games, die Google-TV-App, TalkBack, der Ambient-Bildschirmschoner

Wichtig für mich: Der Streamer ist auch mein Matter/Thread-Hub für Google Home. Bevor Claude irgendetwas anfasste, hat es nachgeschaut, welche Prozesse dahinterstecken (ot-daemon, mediashell, chromecastservice). Die blieben alle unangetastet, und Matter läuft weiter. Den Google Assistant hatte ich erst mit deaktiviert, dann aber für die Mikrofontaste zurückgeholt. Siri gibt es auf Android eben nicht 😢

Als Extra läuft jetzt Private DNS mit AdGuard (settings put global private_dns_specifier dns.adguard-dns.com). YouTube-Werbung blockiert das zwar nicht, aber dafür gibt es ja SmartTube.

Stufe 2: Der neue Launcher

Der Launcher aus dem Repo ist bewusst minimalistisch: reines Java, AndroidX RecyclerView, minSdk 26 und keine INTERNET-Permission. Konfiguriert wird er über eine config.xml beziehungsweise ein Theme als config.json, das zur Laufzeit über einen eigenen ContentProvider (content://com.gathin.tvhome.theme/) aufs Gerät geschoben wird.

Ich hatte kein JDK und kein Android SDK auf dem Mac. Das Repo bringt dafür einen Docker-Build mit, der Build lief also komplett im Container. Das Repo erlaubt für die APK höchstens 576 KiB (589.824 Byte), und das Ergebnis lag mit 574 KiB (588.049 Byte) knapp darunter.

Mein Dock: SmartTube, Apple TV, Twitch, Disney+. Die Tasten der Google-Fernbedienung sind ohne den Google-Launcher erst einmal tot. Der Launcher holt sie über einen Accessibility-Service zurück. Jetzt öffnet die YouTube-Taste (Keycode 190) SmartTube und die Netflix-Taste (191) Apple TV.

Danach habe ich angefangen, mir den Look des aktuellen Apple TV zu wünschen. Ein paar Dinge gingen über Theme-Keys: ein gläsernes Dock, runde Icon-Buttons und ein Fokus-Rahmen in der Farbe der jeweiligen App. Andere nur über Code-Änderungen in meinem lokalen Build:

  • Eigene Icons: Die Buttons oben rechts sind neu gezeichnete, dünne Outline-Vektoren, zum Beispiel zwei Schalter für die Einstellungen. Bewusst keine Kopie von Apples SF Symbols.
  • App-Bibliothek als Raster: Aus der horizontalen Reihe wurde ein bildschirmfüllendes Raster mit sechs Spalten (GridLayoutManager statt LinearLayoutManager), inklusive Spring-Animation auf beiden Achsen.
  • Schrift: Product Sans wollte ich gern, die ist aber Googles Hausschrift und nicht frei lizenziert. Der Launcher nutzt schon Outfit, das kommt optisch sehr nah dran.

Als Hintergrund rotiert alle fünf Minuten ein dunkles Wallpaper. Den Anfang machte eine Startsammlung aus den frei lizenzierten Themes des Repos. Dafür hat Claude alle 113 Bilder nach Helligkeit sortiert und nur die dunklen genommen.

Stufe 4: Eigene Apps, parallel gebaut

Jetzt wurde es interessant. Zwei meiner Wünsche konnte der Launcher prinzipbedingt nicht erfüllen, weil er offline bleiben soll. Claude hat dafür zwei kleine, eigenständige Apps geschrieben, und zwar parallel mit zwei Subagents, die gleichzeitig gebaut und auf dem Fernseher getestet haben.

Sonos-Lautstärkeanzeige

Mein Ton läuft über eine Sonos Arc Ultra. Bisher zeigte Android beim Lautstärke-Ändern nur „+" und „−" an. Der erste Gedanke war HDMI-CEC mit Absolute Volume Behavior. Die Arc Ultra meldet aber set_audio_volume_level: N, sie verrät dem Streamer ihre Lautstärke also schlicht nicht.

Die Lösung ist die lokale UPnP-Schnittstelle der Sonos, ganz ohne Cloud und Account:

  • Discovery per SSDP M-SEARCH nach ZonePlayer:1, bevorzugt ein Modell mit „Arc" im Namen
  • Live-Events per UPnP GENA: Die App abonniert RenderingControl und betreibt dafür einen winzigen HTTP-Server auf dem Fernseher, an den die Sonos jede Änderung schickt
  • Anzeige als TYPE_ACCESSIBILITY_OVERLAY, also über jeder App, ohne extra Overlay-Permission
  • Schnelle Reaktion: Solange die Leiste sichtbar ist, fragt die App zusätzlich alle 120 ms die Lautstärke ab. Von der Änderung bis zur Anzeige vergehen rund 50 ms.

Bonus: Die Fernbedienung ändert die Lautstärke der Sonos in Zweierschritten, das war mir zu langsam. Die App erkennt jetzt einen einzelnen Tastendruck: Kommt innerhalb von 350 ms keine weitere Änderung, ergänzt sie auf ±5. Hält man die Taste gedrückt, läuft die Lautstärke ganz normal hoch. Angezeigt wird direkt der Endwert. Nach oben ist bei 60 Schluss, damit ein versehentlicher Tastendruck nicht die Nachbarn weckt.

Dynamische Wallpaper von Wallhaven

Die zweite App holt neue dunkle Hintergründe über die Wallhaven-API, die für jugendfreie Inhalte keinen API-Key braucht. Ein JobScheduler-Job läuft nur mit Netz, lädt ein paar Bilder, prüft Helligkeit und Detailgrad, dunkelt ab und übergibt sie über denselben ContentProvider an den Launcher. Offline rotiert einfach der Cache aus rund 30 Bildern weiter.

Der erste Wurf mit zufälligen Bildern brachte prompt ein Polandball-Meme auf den Homescreen :D Seitdem kommen nur noch Bilder aus der Topliste mit mindestens 150 Favoriten, ohne Anime und ohne Bilder mit Memes, Text oder Logos (per Tag-Filter). Ein Knopf oben rechts zum Löschen und dauerhaften Sperren des aktuellen Bildes ist gerade in Arbeit.

Wie viel Arbeit war das?

Neu geschrieben, also ohne den Launcher aus dem Repo mit seinen rund 15.400 Zeilen Java:

  • Sonos-App: ~1.950 Zeilen, davon ~1.800 Java, APK 28 KB
  • Wallpaper-App: ~1.540 Zeilen, davon ~1.400 Java
  • Launcher-Anpassungen: +112 / −37 Zeilen
  • Gesamt: rund 3.600 Zeilen, alles plain Java ohne zusätzliche Libraries und durchgehend kommentiert

Als Schätzung, was ein erfahrener Android-Entwickler ohne KI dafür bräuchte:

  • Sonos-App: 25–40 Stunden, vor allem für SSDP, GENA und das Testen am echten Gerät
  • Wallpaper-App: 15–30 Stunden
  • Launcher-Anpassungen: 4–8 Stunden
  • Debloat, Einrichtung und Tests: 6–10 Stunden

In Summe also 50–90 Stunden, grob zwei Arbeitswochen. Bei mir war es ein Abend, an dem ich hauptsächlich Feedback gegeben habe wie „das Wallpaper ist trash" oder „mach mal 5 statt 2".

Ein KI-Agent mit ADB-Zugriff

Wer einer KI eine Shell auf seinem Fernseher gibt, sollte ein paar Dinge beachten:

  • Deaktivieren statt deinstallieren: Alles lässt sich mit tools/restore.sh rückgängig machen.
  • Backup vor der ersten Änderung, nicht danach
  • Kleine Batches mit Screenshot dazwischen, damit man weiß, was etwas kaputt gemacht hat
  • Ein injizierter Tastendruck ist kein Beweis: Fernbedienungstasten, die über einen Accessibility-Service laufen, muss man selbst mit der echten Fernbedienung testen.
  • ADB am Ende wieder schließen: Port 5555 ist unverschlüsselt, und jedes Gerät im LAN könnte sonst eine Shell bekommen. Das Repo prüft das mit tools/adb-closed.py.

Kosten & Lizenzen

  • cleanyourtv steht unter der MIT-Lizenz, die Wallpaper der Startsammlung unter CC0 bzw. CC BY. Die Quellen stehen in den jeweiligen CREDITS.md.
  • Wallhaven lässt sich ohne Key nutzen; die Bilder gehören ihren jeweiligen Urhebern und sind nur für die private Anzeige auf meinem Fernseher gedacht.
  • Claude Code lief über mein bestehendes Abo, im Hintergrund mit Claude Opus 5.5 und Subagents.

Fazit

Der eigentliche Debloat war in einer halben Stunde erledigt, und das Repo nimmt einem mit seinen Sicherheitsnetzen viel Angst vor dem Herumspielen in den Systempaketen. Richtig beeindruckt hat mich aber, was danach kam: Aus „kann man die Sonos-Lautstärke nicht irgendwie anzeigen?" wurde innerhalb von Minuten eine funktionierende App mit UPnP-Event-Abo, und zwar parallel zu einer zweiten App, getestet auf dem echten Gerät.

Unabhängig vom Ergebnis gilt: Den generierten Code habe ich nicht Zeile für Zeile geschrieben, und er ist bisher nur auf genau diesem einen Gerät getestet. Für mein Wohnzimmer ist das völlig okay. Für etwas, das andere Leute installieren sollen, würde ich mir das Ganze noch einmal in Ruhe anschauen.

Author: Peter Gerhards