Skip to content

Repository files navigation

NetPilot — Private DNS & VPN for Android TV (and every Android screen)

NetPilot banner

NetPilot puts the missing Private DNS (DNS-over-TLS) controls of Android TV — and a clean, profile-based VPN manager — behind one beautiful, remote-control-first interface. 100% first-party code: no third-party runtime libraries beyond Google's own AndroidX/Material, no analytics, no ads, no telemetry, no network calls of its own.

Built for Android TV (D-pad/remote first), running everywhere else too — phones and tablets, Android 9.0+ (API 28) → Android 15 (API 35), all densities and screen sizes (24"–80"+ TVs, phones, tablets), light & dark themes.


Why NetPilot exists

Most Android TV builds hide the Private DNS settings screen — but the feature itself is alive and well inside the OS since Android 9. NetPilot drives the system settings directly (Settings.Global → private_dns_mode / private_dns_specifier), the same keys the hidden menu writes.

Because Android (correctly) protects these settings, a one-time permission grant over ADB is needed — no root, ~2 minutes, survives reboots and app updates:

adb shell pm grant app.netpilot android.permission.WRITE_SECURE_SETTINGS

The app walks the user through this with a built-in, copy-paste setup guide and verifies the grant live. Without the grant, everything is still browsable and the VPN works — only system DNS writes are held back.

Features

🔒 Private DNS — two ways

Zero-setup Secure DNS (no ADB, no computer): a first-party DNS-over-TLS tunnel inside VpnService — Android asks for VPN permission once (a normal dialog, remote-friendly) and every DNS lookup is encrypted to your selected provider. Bootstrap resolution happens before the tunnel comes up, so the provider can never recurse into the tunnel. Limited to DNS, nothing else is routed.

System Private DNS (strongest, one-time ADB): drives the real Settings.Global keys — device-wide, kernel-enforced, also covers apps that ignore per-app DNS. Grant:

adb shell pm grant app.netpilot android.permission.WRITE_SECURE_SETTINGS

No computer? Install Termux on any Android phone → pkg install android-tools → adb connect <TV_IP>:5555 → run the grant from the phone over Wi-Fi.

Common to both modes:

  • Multiple profiles (name + DoT hostname, e.g. dns.google, one.one.one.one, dns.adguard-dns.com)
  • Single-active invariant: activating one profile automatically deactivates the previous one — exactly one provider is ever live
  • Modes: Off · Automatic (opportunistic) · Profile (strict hostname)
  • Input validation with dedicated errors for "that's an IP, Private DNS needs a hostname"
  • Live system-state sync via ContentObserver (catches out-of-app changes instantly)

🛡 VPN — works on every supported Android version

Android WireGuard (embedded) IKEv2 (platform) OpenVPN
9 / 10 (TV focus) ✅ in-app, one app — (needs 11+) ✅ engine bridge · embedded core in development
11+ ✅ in-app, one app ✅ in-app (platform engine) ✅ engine bridge · embedded core in development
  • Native platform IKEv2/IPsec (Android 11+): the OS implements the protocol — NetPilot only provisions and starts/stops it via VpnManager/Ikev2VpnProfile (PSK or username/password + CA cert)
  • OpenVPN profiles: import .ovpn files with a built-in first-party config parser (remotes, ciphers, inline CA/cert/key blocks, auth style)
  • WireGuard — embedded engine, ONE app: the official wireguard-android tunnel library (Apache-2.0, userspace wireguard-go) ships inside NetPilot. Import your server's .conf (WgConfigCheck pre-validates it), connect, done — this is the DEFAULT type on Android 9/10, where the platform IKEv2 API does not exist. Runs through NetPilot's own state machine, notification and Turn-off action; the server needs WireGuard enabled (every serious server/provider supports it alongside OpenVPN)
  • OpenVPN: embedded engine (v2.3.0) — the official OpenVPN 3 C++ core (AGPL-3.0) is compiled by CI into libovpncore.so and linked behind the core.vpn.VpnDataChannel seam: import a .ovpn, connect in-app, one app, Android 9+. Release builds ship the engine; the connection state is the core's own CONNECTED event, never a guess. On builds without the native library NetPilot drives the official open-source OpenVPN for Android app through its documented external control API instead (same profile, honest labeling either way)
  • Android keeps one VPN active system-wide; the UI mirrors that honestly — an engine-app tunnel is reported as "Engine VPN active", never as NetPilot's own

🧭 TV-first UX

  • Left navigation rail on TV (Netflix-style), bottom navigation on phones/tablets
  • Every control is D-pad reachable; focus ring + lift + zoom make focus unmistakable at 10 feet
  • Big type, ≥48 dp targets, high-contrast palettes for both themes

Also on board

  • Dashboard: protection state hero, quick toggles, live network facts (interface, effective DNS)
  • Quick Settings tiles: one-tap Private DNS and VPN toggles from the shade — boundaryless, run only while the shade is open (Android 9+; subtitles on 10+)
  • Per-app VPN (WireGuard): include/exclude which apps use the tunnel — written into the profile's .conf as IncludedApplications/ExcludedApplications, applied natively by the engine
  • Restart-proof access: the ADB grant survives TV power cycles; if access is ever lost (Shizuku-only setup after a restart, cleared data), the first-launch guide re-appears automatically with the fix (see docs/ACCESS-PERSISTENCE.md)
  • Optional boot reconnect (Settings, default off): one reconnect attempt at power-on for the embedded WireGuard tunnel — no background retry loops
  • App shortcuts: long-press the icon → Private DNS / VPN
  • Zero-dormancy battery: when nothing is protecting, nothing runs or listens — no background services, listeners, polling, or wake locks (see docs/PERFORMANCE.md)
  • Settings: theme (system/light/dark), permission status, profile export/import (JSON via SAF), privacy statement, in-app open-source licenses
  • Advisory (not a blocker) when VPN + strict Private DNS run together — they can coexist; strict DoT then encrypts lookups even inside the tunnel

Build it yourself

git clone <this repo> && cd netpilot
./gradlew :app:assembleDebug          # → app/build/outputs/apk/debug/app-debug.apk
./gradlew :app:testDebugUnitTest      # 75 JVM tests (JUnit + Robolectric)
./gradlew :app:lintDebug              # Android Lint gate

Or just push — GitHub Actions builds, tests, lints and scans on every commit (see below).

Install

  1. Sideload the APK (Settings → Apps → Install unknown apps on TV, or adb install app-debug.apk)
  2. Open NetPilot → follow the one-time setup guide for the ADB grant
  3. Add a DNS profile (e.g. Cloudflare / one.one.one.one) → activate. Done.

CI/CD & security

Workflow What it does
CI (ci.yml) Unit tests → Lint → APK build → instrumented tests on real emulators (API 29 & 34, one leg with the ADB permission granted to exercise true system writes)
Security (security.yml) Gitleaks secret scanning, PR dependency review (vuln + license gate), build-hardening assertions (no cleartext, no debuggable, backups stay excluded)
Release (release.yml) Tag v* → verification gate → signed APK + AAB + SHA-256 checksums + changelog; refuses to publish unsigned
CodeQL Static security analysis (Java/Kotlin, security-extended)

Signing uses repository secrets (ANDROID_KEYSTORE_B64, …) — never committed files. Full details: docs/RELEASE.md, docs/SECURITY.md.

Documentation

License & attribution

App code: MIT (see LICENSE). Runtime dependencies: first-party Android libraries only (AndroidX, Material Components, Kotlin stdlib — Apache 2.0). Build/test tooling (Gradle, JUnit, Robolectric, Espresso) is open-source but never ships in the APK; the full list lives in-app under Settings → Open-source licenses.

About

NetPilot — Private DNS & VPN for Android TV (and every Android screen)

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages