TV Clock — always-on overlay

An Android TV / Google TV app that pins the current time to the top-right corner, over every other app.

Kotlin Package com.tatav.tvclock Gradle 8.9 · AGP 8.7.2 · Kotlin 1.9.24 · JDK 21 minSdk 21 · targetSdk 33 · compileSdk 34 Build: SUCCESS ✓ Verified on Android TV emulator ✓

0 · Proof it works

Built the APK, spun up a Google TV emulator (the android-34/android-tv arm64 image you already had), installed the app, granted the overlay permission, and pressed Home. The clock renders on top of the Google TV launcher — exactly the goal:

TV Clock overlay showing 11:14 PM over the Google TV home screen
Top-right corner: 11:14 PM in a translucent pill, drawn over the launcher's content rows. The foreground service reported isForeground=true and stayed alive after leaving the app.

1 · Kotlin or Java? — Kotlin is totally fine

There is no Java-only restriction on Android TV or Google TV. They run the same Android runtime as phones, and Kotlin compiles to the same bytecode Java does. This app is written in Kotlin — three small files. Anything you can do on TV in Java you can do in Kotlin and vice-versa; the SDK, Leanback, WindowManager, services — all identical.

2 · The core idea

A normal app can only draw inside its own window, so it disappears the moment you switch to Netflix or YouTube. To float something on top of everything, Android has one mechanism: a system overlay window. You ask the WindowManager to add a view of type TYPE_APPLICATION_OVERLAY, which the system composites above other apps. That view is a plain TextView showing the time, refreshed every second.

To keep that window alive after you leave the app, it's owned by a foreground service (not an Activity). Foreground services survive app-switching and show a small notification so the system won't kill them.

3 · How the pieces fit together

MainActivity.kt

The launcher tile. Does almost nothing: checks the overlay permission and starts the service, or — if the permission is missing — prints the exact ADB command to grant it. The clock is not drawn here.

ClockOverlayService.kt

A foreground service. On start it adds the TextView to the WindowManager at Gravity.TOP or END and ticks every second. Flags NOT_FOCUSABLE + NOT_TOUCHABLE mean it never steals remote-control input.

BootReceiver.kt

Listens for BOOT_COMPLETED and restarts the overlay after the TV reboots — so you never have to open the app again once it's set up.

Manifest / Leanback

Declares the LEANBACK_LAUNCHER intent + a banner so the app appears on the TV home row, and requests SYSTEM_ALERT_WINDOW, FOREGROUND_SERVICE, RECEIVE_BOOT_COMPLETED.

4 · What I set up on this machine

ItemDetail
Android SDKAlready present at ~/Library/Android/sdk (platforms 34–36, build-tools 34–36, and an android-34/android-tv arm64 system image).
JDKYou had only JDK 23 & 25 — both too new for the Android Gradle Plugin. Installed OpenJDK 21 (LTS) via Homebrew: brew install openjdk@21.
Gradle wrapperPinned Gradle 8.9 (./gradlew) so builds are reproducible regardless of the system Gradle (which was 9.0, too new for AGP 8.7).
Emulator (AVD)Created tv_clock from the TV image and booted it to verify the overlay live.
ProjectHand-written from scratch — no Android Studio. Pure framework, zero third-party dependencies.

5 · Try it on the emulator

# create a TV AVD from the image you already have (once)
echo no | ~/Library/Android/sdk/cmdline-tools/latest/bin/avdmanager create avd \
  -n tv_clock -k "system-images;android-34;android-tv;arm64-v8a" -d tv_1080p

# boot it
~/Library/Android/sdk/emulator/emulator -avd tv_clock -gpu auto &

# build + install + grant + launch (the emulator counts as a "device")
./deploy.sh

6 · Deploy to your real TV

1

Enable Developer options + ADB on the TV

On the TV: Settings → System → About → click Build 7 times. Then in Developer options turn on USB debugging / Network debugging. Note the TV's IP under Settings → Network.
2

One command does the rest

./deploy.sh 192.168.1.50    # your TV's IP
It connects over the network, installs the APK, grants the overlay permission, and launches the app. Accept the "Allow debugging?" prompt on the TV the first time.
The one non-obvious step (baked into deploy.sh): Android TV usually has no on-screen setting for "Draw over other apps", so the normal permission dialog never appears. It's granted over ADB instead:
adb shell appops set com.tatav.tvclock SYSTEM_ALERT_WINDOW allow
Without this, the app runs but shows no clock.

7 · Handy commands

# rebuild the APK (JDK 21 required for the build)
JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home ./gradlew assembleDebug

adb shell am force-stop com.tatav.tvclock   # stop the clock
adb uninstall com.tatav.tvclock             # remove it

8 · Good to know / limits

DRM / secure video: A few streaming apps mark their video surface "secure". Over those, the system may hide all overlays — so the clock can briefly vanish during protected playback. Over the launcher, menus, and most apps it stays visible.

9 · Project layout

Projects/tv-overlay-clock/
├── build.gradle            # AGP 8.7.2 + Kotlin 1.9.24 plugins
├── settings.gradle         # google() + mavenCentral() repos
├── gradle.properties / local.properties
├── deploy.sh               # build + install + grant + launch
├── gradlew / gradle/…       # pinned Gradle 8.9 wrapper
├── docs/verified-on-emulator.png
├── EXPLAINER.html          # this file
└── app/
    ├── build.gradle         # sdk levels, Kotlin jvmTarget 17
    └── src/main/
        ├── AndroidManifest.xml
        ├── java/com/tatav/tvclock/
        │   ├── MainActivity.kt          # launcher; starts service
        │   ├── ClockOverlayService.kt   # the overlay itself
        │   └── BootReceiver.kt          # restart on reboot
        └── res/
            ├── values/strings.xml
            └── drawable/banner.xml     # TV launcher banner