Features: - Hidden config.json in assets for per-deployment customization - Configure target URL, app name, and branding - Optional WireGuard VPN with auto-connect - Optional Tor routing via Orbot - Custom theme colors - Configuration-driven app behavior Configuration file location: app/src/main/assets/config.json Example configuration: config.example.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
100 lines
3.0 KiB
Kotlin
100 lines
3.0 KiB
Kotlin
package uk.silverlabs.silverdroid.vpn
|
|
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.net.VpnService
|
|
import android.util.Log
|
|
import com.wireguard.android.backend.GoBackend
|
|
import com.wireguard.config.Config
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import uk.silverlabs.silverdroid.config.VpnConfig
|
|
|
|
/**
|
|
* Manages WireGuard VPN connections
|
|
*/
|
|
class WireGuardManager(private val context: Context) {
|
|
|
|
private val backend = GoBackend(context)
|
|
private val _connectionState = MutableStateFlow(VpnState.DISCONNECTED)
|
|
val connectionState: StateFlow<VpnState> = _connectionState
|
|
|
|
suspend fun connect(vpnConfig: VpnConfig): Result<Unit> {
|
|
return try {
|
|
// Check VPN permission
|
|
val intent = VpnService.prepare(context)
|
|
if (intent != null) {
|
|
return Result.failure(Exception("VPN permission required"))
|
|
}
|
|
|
|
// Build WireGuard config
|
|
val configText = buildWireGuardConfig(vpnConfig)
|
|
val config = Config.parse(configText.byteInputStream())
|
|
|
|
// Set up tunnel
|
|
val tunnel = backend.tunnels.firstOrNull() ?: backend.createTunnel(
|
|
"silverdroid_vpn",
|
|
config,
|
|
null
|
|
)
|
|
|
|
// Connect
|
|
backend.setState(tunnel, com.wireguard.android.backend.Tunnel.State.UP)
|
|
|
|
_connectionState.value = VpnState.CONNECTED
|
|
Log.i(TAG, "WireGuard VPN connected successfully")
|
|
|
|
Result.success(Unit)
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Failed to connect VPN", e)
|
|
_connectionState.value = VpnState.ERROR
|
|
Result.failure(e)
|
|
}
|
|
}
|
|
|
|
suspend fun disconnect() {
|
|
try {
|
|
backend.tunnels.forEach { tunnel ->
|
|
backend.setState(tunnel, com.wireguard.android.backend.Tunnel.State.DOWN)
|
|
}
|
|
_connectionState.value = VpnState.DISCONNECTED
|
|
Log.i(TAG, "WireGuard VPN disconnected")
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Error disconnecting VPN", e)
|
|
}
|
|
}
|
|
|
|
private fun buildWireGuardConfig(vpnConfig: VpnConfig): String {
|
|
val peers = vpnConfig.peers.joinToString("\n\n") { peer ->
|
|
"""
|
|
[Peer]
|
|
PublicKey = ${peer.publicKey}
|
|
Endpoint = ${peer.endpoint}
|
|
AllowedIPs = ${peer.allowedIps.joinToString(", ")}
|
|
PersistentKeepalive = ${peer.persistentKeepalive}
|
|
""".trimIndent()
|
|
}
|
|
|
|
return """
|
|
[Interface]
|
|
PrivateKey = ${vpnConfig.privateKey}
|
|
Address = ${vpnConfig.address}
|
|
${if (vpnConfig.dns.isNotEmpty()) "DNS = ${vpnConfig.dns.joinToString(", ")}" else ""}
|
|
|
|
$peers
|
|
""".trimIndent()
|
|
}
|
|
|
|
companion object {
|
|
private const val TAG = "WireGuardManager"
|
|
}
|
|
}
|
|
|
|
enum class VpnState {
|
|
DISCONNECTED,
|
|
CONNECTING,
|
|
CONNECTED,
|
|
DISCONNECTING,
|
|
ERROR
|
|
}
|