The problem

The stock Google TV Home is a catalogue: rows of recommended content, banners, ads, and somewhere underneath the apps I actually use. On my Haier H65M90EUX it is also slow: measured during a normal session, 8.5% of frames jank and the launcher takes about 99 MB of memory.

I wanted the opposite: a quiet Home that turns on and shows the time, a wallpaper and the five apps I open every day. The stated inspiration is the Apple TV philosophy, but without using any of its assets. The result is Aether TV, a launcher written from scratch.

The Home: date, clock behind the mountains, glass dock and shortcuts

At a glance

Version1.8.1, package com.aether.tv
DeviceHaier H65M90EUX, Android 14 (API 34), 32-bit MediaTek MT5889 (4× Cortex-A73), Mali-G52 GPU, 2.78 GB of RAM
UI1920×1080 logical on a 4K panel, 60 Hz (the only display mode the system exposes to apps)
StackKotlin + Android Views. No Jetpack Compose, no AndroidX, zero dependencies
Code43 Kotlin files, about 4,900 lines
APK331 KB, wallpaper included
Timelinefrom the first ADB diagnosis to 1.8.1 in three days, 28 to 30 October 2026

A contrarian choice: Views, not Compose

Today a new Android app is almost always written in Jetpack Compose, and HNCC uses it too. For a launcher on this hardware, though, the numbers don’t add up. The TV has a 32-bit SoC and, with Netflix open, about 800 MB of free RAM. A launcher gets killed and restarted often, so cold start matters more than anything.

I went with classic Android Views: native, predictable D-pad focus, animations with ViewPropertyAnimator on the RenderThread, no libraries to load. Every animation is interruptible: the remote never waits for a transition to finish.

Features

Home and dock

The layout is that of a lock screen: the date above a large centred clock, a full-screen photo and a dock with five apps.

  • Liquid glass: every button is frosted glass with a rim of light, brighter at the top left, and a soft inner glow.
  • Depth effect: the mountains pass in front of the clock. Aether finds the skyline of any photo on its own (texture analysis on a downscaled image, then refinement at full resolution) and redraws over the digits only the slice of wallpaper it needs. If the photo has no sky, the effect turns itself off.
  • Below the dock there is a row of shortcuts (Network, HNCC, Screensaver, Inputs, Account, TV settings) and at the top right weather, Wi-Fi and profile. Weather comes from Open-Meteo, no account needed: only the city name is sent.
  • Apps open on the press of OK, not on release: it sounds like a detail, but you can feel it.

All apps

The grid of every app, over the blurred wallpaper

The grid shows every installed app with its TV banner or icon, and updates itself when I install or remove something, without restarts.

There is no hard-coded package name in the code: the dock uses the favourites I picked or, without them, an automatic ranking based on system metadata (video category first, then audio, then games, then user-installed apps).

Settings

Aether's settings: dock favourites, interface, motion, diagnostics

Settings follow the same style, and since 1.8 they are not only Aether’s: I also rebuilt the TV settings, meaning network, Bluetooth, picture, sound, HDMI inputs, screensaver, date and time, apps, TV name and accounts.

  • Picture and sound write straight into the Settings.Global keys (picture_*, sound_*) watched by the TV’s MediaTek service: changes apply live, and Haier’s original page shows the same values.
  • The remote’s Settings or Input key opens a glass quick panel over any app, with volume, picture and sound modes, inputs and network.
  • To make that work, an accessibility service intercepts only those two keys and the entry pages of Google’s settings, and opens the matching Aether page instead.

Screensaver

The screensaver preview: just wallpaper, date and time

It is a system DreamService: the Home wallpaper with only the date and time. Every minute the group shifts by a few pixels to protect the panel from burn-in. Between shifts the GPU is idle: about 90 frames per minute, all of them during the one-and-a-half-second move.

Opening an app

From OK on the dock to YouTube in full screen

Aether hands the request to the system about 50 ms after OK is pressed. At first there was a ~300 ms zoom from the tile: I replaced it with a 50 ms transition that keeps the system’s preview, so the app appears in full screen right away. With YouTube already in memory, the app’s first frame arrives about 47 ms later.

I also tried FLAG_ACTIVITY_NO_ANIMATION, which looks faster. Measured, it hides the preview and keeps Aether on screen for longer: dropped.

The method: measure everything on the real TV

This is the part of the project I’m proudest of. Every choice was measured on the television, in release builds, with system tools (am start -W, dumpsys gfxinfo with framestats, atrace, dumpsys meminfo, top) and my own scripts. The rule was simple: a change stayed only if the improvement was measurable.

BottleneckFixResult
Gradient background recomputed every framebitmap precomputed once−3.5 ms of GPU per frame
Font weights via fontVariationSettingsthe system Roboto’s registered weightsinflate from 103 to 16 ms; Settings from ~330 to ~80 ms
Glass blurblurred copy precomputed at 120×67, scaled up by the GPU: never blur at runtimedock GPU p50 of 3.8 ms
Full-screen crossfade between sharp and blurred wallpaperinstant swap, only the grid fadespanel jank from 22% to 1–2.5%
Offscreen layers in fadeshasOverlappingRendering = false on animated viewsper-frame layers gone
Startup: labels and banners read before showing the dockon-disk snapshot of the last dock, verified in the backgrounddock ready in 27 ms
Wallpaper decoded in parallel with the first frameloaded after the first frame, at low priorityfirst frames without delay

Just as useful are the experiments dropped after measuring: AOT compilation and baseline profiles (no gain), withLayer() on animations (worse), blurred shadows under buttons (p99 from 13 to 27 ms), a rim of light on every grid tile.

The numbers

MetricValue
Cold start, full dockfrom 1.0–1.9 s in the first version to 375–457 ms in 1.0; 464 ms with the depth effect
Back to Home from another app61–125 ms, same focus, even after Netflix and YouTube
Dock navigation0% jank (p99 10–16 ms)
Opening Settings73–109 ms
Memory (PSS)about 48–65 MB, no growth after repeated cycles
CPU at idle0%: no polling, no background services, clock updated once a minute
Google TV launcher, for reference8.54% jank, p50 21 ms, PSS ~99 MB (real session, indicative comparison only)

Fighting the firmware

Writing the Home was the easy part. Getting the TV to use it, much less so.

  • Becoming the Home is not a given. The Google TV launcher declares priority 2 on the HOME filter, while third-party apps are capped at 0. Even with the Home role assigned to Aether, the Home key always went back to Google TV. With Google TV disabled, Home fell through to the Setup Wizard’s recovery page: priority 1, protected, black screen. The fix, a deliberate and fully reversible choice, was to disable both Google TV and the Setup Wizard over ADB. Now Aether starts at power-on and answers the Home key.
  • Simulated keys lie. Keys sent with adb shell input keyevent skip accessibility filters and are instantaneous, and that hid two bugs: the “unreachable” Settings key and a screensaver preview that closed itself. For realistic tests I built a virtual remote via uinput, with the same layout as the Google remote.
  • Google hides its own accounts from other apps, even with the GET_ACCOUNTS permission: Aether’s Account page reads the names from a file prepared over ADB.
  • Without root, some things stay Google’s: keyboard, volume bar, permission pop-ups, Assistant and the system pages, which Aether marks with ”↗”.
  • Special permissions (WRITE_SECURE_SETTINGS, the WRITE_SETTINGS app-op, the accessibility service) are granted once with a script, and the same script revokes them.

Design

The default wallpaper, “Valle di nebbia” (a valley with a sea of fog at dawn), is not a photo: it is generated by a Python script, which is why the APK stays so light. You can also use your own photo, cropped to 16:9: in the videos I use a different one from the default.

Animations come in three levels (Full, Reduced, Off). The UI is in English, while app names stay in the system language.

Limits

  • It is designed and verified on a single model, the Haier H65M90EUX: it is untested on other TVs.
  • Smoothness measurements use simulated keys: latency with the physical remote has not been measured.
  • The APK is signed with a debug key, so updates need the same key.
  • The 1.7 measurements were disturbed by DAZN running in the background (about 200% CPU) and are not comparable with earlier ones.

Wrapping up

Aether started from annoyance at a Home full of things I never asked for, and turned into an exercise in discipline: don’t optimise on instinct. Many ideas that seemed faster, like dropping the opening animation or precompiling the code, weren’t once the numbers came in.

The TV now turns on to a clock, a mountain and five apps. It weighs 331 KB and, when I’m not touching it, it uses nothing at all.