Skip to content
Snippets Groups Projects
Commit 7809c300 authored by kr328's avatar kr328
Browse files

Refactor: refactor network observer

parent 7e46d751
No related branches found
No related tags found
No related merge requests found
...@@ -62,9 +62,9 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De ...@@ -62,9 +62,9 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
true true
} }
network.onEvent { e -> network.onEvent { n ->
if (Build.VERSION.SDK_INT in 22..28) @TargetApi(22) { if (Build.VERSION.SDK_INT in 22..28) @TargetApi(22) {
setUnderlyingNetworks(e.network?.let { arrayOf(it) }) setUnderlyingNetworks(n?.let { arrayOf(it) })
} }
false false
......
package com.github.kr328.clash.service.clash.module package com.github.kr328.clash.service.clash.module
import android.app.Service import android.app.Service
import android.content.Intent
import android.net.* import android.net.*
import android.os.PowerManager import android.os.Build
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.github.kr328.clash.common.log.Log import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.core.Clash import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.service.util.resolveDns import com.github.kr328.clash.service.util.resolveDns
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class NetworkObserveModule(service: Service) : class NetworkObserveModule(service: Service) : Module<Network?>(service) {
Module<NetworkObserveModule.NetworkChanged>(service) { private data class Action(val type: Type, val network: Network) {
data class NetworkChanged(val network: Network?) enum class Type { Available, Lost, Changed }
}
private val connectivity = service.getSystemService<ConnectivityManager>()!! private val connectivity = service.getSystemService<ConnectivityManager>()!!
private val networks: Channel<Network?> = Channel(Channel.CONFLATED) private val actions = Channel<Action>(Channel.UNLIMITED)
private val request = NetworkRequest.Builder().apply { private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
}.build() }.build()
private val callback = object : ConnectivityManager.NetworkCallback() { private val callback = object : ConnectivityManager.NetworkCallback() {
private var network: Network? = null
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
if (this.network != network) actions.trySendBlocking(Action(Action.Type.Available, network))
networks.trySend(network)
this.network = network
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
if (this.network == network)
networks.trySend(network)
} }
}
private fun register(): Result<Unit> { override fun onLost(network: Network) {
return runCatching { actions.trySendBlocking(Action(Action.Type.Lost, network))
connectivity.registerNetworkCallback(request, callback)
}.onFailure {
Log.w("Observe network change: $it", it)
} }
}
private fun unregister(): Result<Unit> { override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
return runCatching { actions.trySendBlocking(Action(Action.Type.Changed, network))
connectivity.unregisterNetworkCallback(callback)
} }
} }
override suspend fun run() { override suspend fun run() {
val screenToggle = receiveBroadcast(false, Channel.CONFLATED) { try {
addAction(Intent.ACTION_SCREEN_ON) connectivity.registerNetworkCallback(request, callback)
addAction(Intent.ACTION_SCREEN_OFF) } catch (e: Exception) {
} Log.w("Observe network failed: $e", e)
if (service.getSystemService<PowerManager>()?.isInteractive != false) { return
register()
} }
try { try {
var current: Network? = null
val networks = mutableSetOf<Network>()
while (true) { while (true) {
val quit = select<Boolean> { val action = actions.receive()
screenToggle.onReceive {
when (it.action) {
Intent.ACTION_SCREEN_ON ->
register().isFailure
Intent.ACTION_SCREEN_OFF ->
unregister().isFailure
else ->
false
}
}
networks.onReceive {
val dns = connectivity.resolveDns(it)
Clash.notifyDnsChanged(dns) when (action.type) {
Action.Type.Available -> {
networks.add(action.network)
}
Action.Type.Lost -> {
networks.remove(action.network)
}
Action.Type.Changed -> {
if (current == action.network) {
val dns = connectivity.resolveDns(action.network)
Log.d("Network changed, system dns = $dns") Clash.notifyDnsChanged(dns)
enqueueEvent(NetworkChanged(it)) Log.d("Current network changed: ${action.network}: $dns")
}
false continue
} }
} }
if (quit) {
return current = networks.maxByOrNull {
connectivity.getNetworkCapabilities(it)?.let { cap ->
TRANSPORT_PRIORITY.indexOfFirst { cap.hasTransport(it) }
} ?: -1
} }
val dns = connectivity.resolveDns(current)
Clash.notifyDnsChanged(dns)
enqueueEvent(current)
Log.d("Available network changed: $current of $networks: $dns")
} }
} finally { } finally {
withContext(NonCancellable) { withContext(NonCancellable) {
unregister() enqueueEvent(null)
Clash.notifyDnsChanged(emptyList()) Clash.notifyDnsChanged(emptyList())
runCatching {
connectivity.unregisterNetworkCallback(callback)
}
} }
} }
} }
companion object {
private val TRANSPORT_PRIORITY = sequence {
yield(NetworkCapabilities.TRANSPORT_CELLULAR)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
yield(NetworkCapabilities.TRANSPORT_LOWPAN)
}
yield(NetworkCapabilities.TRANSPORT_BLUETOOTH)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
yield(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
}
yield(NetworkCapabilities.TRANSPORT_WIFI)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
yield(NetworkCapabilities.TRANSPORT_USB)
}
yield(NetworkCapabilities.TRANSPORT_ETHERNET)
}.toList()
}
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment