Automatic per-app VPN switching for Android TV.
Pick a VPN location for each app once (BBC iPlayer → UK, Netflix → US, YouTube → no VPN), and UniVPN switches the WireGuard tunnel whenever that app comes to the foreground. No more opening a VPN app and changing servers every time you change what you're watching.
Built for and tested on the NVIDIA Shield TV. It targets Android TV devices running Android 6.0 or later; the UI is designed for a D-pad remote.
- Per-app routing. Each app gets a route:
- a VPN profile: switch to that tunnel when the app opens
- No VPN: drop the tunnel
- Passthrough: keep whatever tunnel is already up (useful for launchers, keyboards, settings)
- Sensible defaults. New streaming/audio apps start on No VPN and everything else on Passthrough, so the tunnel isn't torn down every time you visit the home screen.
- Provider accounts. Sign in with Mullvad or Private Internet Access and UniVPN generates WireGuard configs for the servers you choose. Configs older than 7 days are refreshed automatically.
- Any WireGuard config. Import
.conffiles from any WireGuard provider or your own server. - Web import. No typing passwords with a remote: open the Profiles screen and UniVPN
shows an address (e.g.
http://192.168.1.20:8080) and a one-time PIN. Visit the address from a phone or laptop on the same network and enter the PIN to upload configs or provider credentials. - Status overlay. Optional on-screen chip showing the active profile and latency, toggleable from the notification.
- Survives reboots and standby. Optional start on boot; routing resumes when the device wakes up.
VpnSwitcherService polls UsageStatsManager for foreground app changes. When the
foreground app's route needs a different tunnel, it tears down the current WireGuard
tunnel and brings up the new one using the official WireGuard Go backend.
Things to know:
- The tunnel is device-wide. Android allows one VPN at a time, so while an app is in the foreground, all traffic on the device, including background apps, uses that app's route.
- There is no kill switch. While one tunnel is going down and the next is coming up, traffic can briefly leave without a VPN. Don't rely on UniVPN where that matters.
There is no Play Store release yet. Download the latest APK from Releases (or build it yourself), then sideload it:
adb connect <device-ip>:5555 # or connect over USB
adb install univpn-<version>.apkTo get updates automatically, add https://github.com/grifmo/univpn to
Obtainium.
Every release APK is signed with the same key. Its certificate's SHA-256 fingerprint is:
60:3B:11:16:39:BC:93:88:C2:EB:AC:2B:4D:6B:4C:BB:1E:8F:E7:86:1D:6C:E4:78:C3:0C:05:7D:15:6D:52:5D
Check an APK with apksigner from the Android SDK build-tools:
apksigner verify --print-certs univpn-<version>.apk
# Signer #1 certificate SHA-256 digest: 603b111639bc9388c2ebac2b4d6b4cbb1e8fe7861d6ce478c30c057d156d525dEach release also includes SHA256SUMS.txt for checking the download itself.
UniVPN needs usage access to see which app is in the foreground. Android TV often hides this setting, so grant it with ADB:
adb shell appops set com.univpn.app GET_USAGE_STATS allowOn the Shield you can also use Settings → Device Preferences → Security & restrictions → Usage access. For the optional status overlay:
adb shell appops set com.univpn.app SYSTEM_ALERT_WINDOW allowOn first launch Android asks you to approve UniVPN as a VPN. Accept it.
- Open Profiles. Add a provider account, or note the web import address and PIN and upload a
WireGuard
.conffrom another device. - Open App Routes, select an app, and pick the profile it should use.
- Launch that app. The tunnel switches automatically.
Requirements: the Android SDK (platform 34) and an internet connection. Gradle downloads the JDKs it needs (21 for the daemon, 17 for compilation) if you don't have them.
# Point Gradle at your SDK, either via ANDROID_HOME or local.properties:
echo "sdk.dir=/path/to/Android/sdk" > local.properties
./gradlew :univpn:assembleDebug
# → univpn/build/outputs/apk/debug/univpn-debug.apkInstrumented tests (needs a connected device or emulator):
./gradlew :univpn:connectedDebugAndroidTestunivpn/src/main/kotlin/com/univpn/app/
├── service/ VpnSwitcherService (foreground watcher + tunnel switching), status overlay
├── tunnel/ TunnelManager interface, WireGuard implementation
├── provider/ Mullvad / PIA connectors, credential store, config freshness
├── data/ Room database, app routes, default-route seeding
├── server/ LAN web import server (NanoHTTPD)
├── receiver/ Boot, package-removed, overlay toggle
└── ui/ Leanback TV UI
UI design notes and the visual design system live in docs/DESIGN.md.
- Certificate pinning for provider APIs
- More providers (NordVPN connector exists but is disabled; Surfshark)
- Play Store release
Contributions are welcome. Please open an issue to discuss larger changes first.
See SECURITY.md for known limitations and how to report vulnerabilities.
UniVPN is free software: you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version (SPDX: GPL-3.0-or-later).
UniVPN is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
Bundled fonts and other third-party components are listed in THIRD_PARTY_NOTICES.md.
UniVPN is not affiliated with WireGuard, Mullvad, Private Internet Access or NVIDIA. WireGuard is a registered trademark of Jason A. Donenfeld.
Fixed in v0.9.2. Please update.
v0.9.0 and v0.9.1 had two serious bugs:
- Mullvad profiles generated in the app stop working after 7 days. The automatic refresh writes a server address that can't be resolved, so the tunnel fails to start.
- A failed connection still shows the profile as active. If a tunnel can't start, UniVPN keeps displaying the profile as active and doesn't retry. Traffic then leaves the device unprotected.
Together, these mean a Mullvad profile can look connected after a week while nothing is protected. v0.9.2 repairs affected Mullvad profiles automatically.
v0.9.2 also fixes the other findings from a community review, tracked in Issues: undetected tunnel drops, the 10-second recovery window, the "Default" route disconnecting, the unauthenticated import server, and private keys stored in plain text.