amnezia-client/client/android/utils/src/main/kotlin/net/NetworkState.kt
albexk d63bf15011
Android qt 6.7.3 (#1143)
* Up Qt to 6.7.3

* Bump version to 4.8.2.0

* Raise the minimum Android version to 8 (API 26)

* Update version code to separate versions for new and old Androids

* Fix mouse not working on TVs

* Refactor logging

* Bump version code
2024-10-18 10:52:24 +01:00

117 lines
4.2 KiB
Kotlin

package org.amnezia.vpn.util.net
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkRequest
import android.os.Build
import android.os.Handler
import androidx.core.content.getSystemService
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.delay
import org.amnezia.vpn.util.Log
private const val TAG = "NetworkState"
class NetworkState(
private val context: Context,
private val onNetworkChange: () -> Unit
) {
private var currentNetwork: Network? = null
private var validated: Boolean = false
private var isListenerBound = false
private val handler: Handler by lazy(NONE) {
Handler(context.mainLooper)
}
private val connectivityManager: ConnectivityManager by lazy(NONE) {
context.getSystemService<ConnectivityManager>()!!
}
private val networkRequest: NetworkRequest by lazy(NONE) {
NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
}
private val networkCallback: NetworkCallback by lazy(NONE) {
object : NetworkCallback() {
override fun onAvailable(network: Network) {
Log.v(TAG, "onAvailable: $network")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
checkNetworkState(network, networkCapabilities)
}
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
if (currentNetwork == null) {
currentNetwork = network
validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
} else {
if (currentNetwork != network) {
currentNetwork = network
validated = false
}
if (!validated) {
validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
if (validated) onNetworkChange()
}
}
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
}
override fun onLost(network: Network) {
Log.v(TAG, "onLost: $network")
}
}
}
suspend fun bindNetworkListener() {
if (isListenerBound) return
Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else {
val numberAttempts = 300
var attemptCount = 0
while(true) {
try {
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
break
} catch (e: SecurityException) {
Log.e(TAG, "Failed to bind network listener: $e")
// Android 11 bug: https://issuetracker.google.com/issues/175055271
if (e.message?.startsWith("Package android does not belong to") == true) {
if (++attemptCount > numberAttempts) {
throw e
}
delay(1000)
continue
} else {
throw e
}
}
}
}
isListenerBound = true
}
fun unbindNetworkListener() {
if (!isListenerBound) return
Log.d(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false
currentNetwork = null
validated = false
}
}