feat: add network availability monitoring

This commit is contained in:
andrekir 2023-07-20 18:42:50 -03:00 committed by Andre K
parent dabbcf6ef4
commit 2d30fd89bc
5 changed files with 62 additions and 21 deletions

View file

@ -210,7 +210,7 @@ class BTScanModel @Inject constructor(
}
// Include Network Service Discovery
nsdRepository.resolvedList?.forEach { service ->
nsdRepository.resolvedList.value.forEach { service ->
addDevice(TCPDeviceListEntry(service))
}

View file

@ -1,11 +1,16 @@
package com.geeksville.mesh.repository.nsd
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkRequest
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.CoroutineDispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
@ -17,18 +22,38 @@ import javax.inject.Singleton
class NsdRepository @Inject constructor(
private val dispatchers: CoroutineDispatchers,
private val nsdManagerLazy: dagger.Lazy<NsdManager?>,
private val connectivityManager: dagger.Lazy<ConnectivityManager>,
) : Logging {
private val resolveQueue = Semaphore(1)
private var hostsList: ArrayList<NsdServiceInfo>? = null
private val availableNetworks: HashSet<Network> = HashSet()
val networkAvailable: Flow<Boolean> = callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
availableNetworks.add(network)
trySend(availableNetworks.isNotEmpty()).isSuccess
}
val resolvedList: List<NsdServiceInfo>? get() = hostsList
override fun onLost(network: Network) {
availableNetworks.remove(network)
trySend(availableNetworks.isNotEmpty()).isSuccess
}
}
val networkRequest = NetworkRequest.Builder().build()
connectivityManager.get().registerNetworkCallback(networkRequest, callback)
awaitClose { connectivityManager.get().unregisterNetworkCallback(callback) }
}
private val resolveQueue = Semaphore(1)
private val hostsList = mutableListOf<NsdServiceInfo>()
private val _resolvedList = MutableStateFlow<List<NsdServiceInfo>>(emptyList())
val resolvedList: StateFlow<List<NsdServiceInfo>> get() = _resolvedList
private val _networkDiscovery: Flow<NsdServiceInfo> = callbackFlow {
val discoveryListener = object : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
debug("Service discovery started: $regType")
hostsList?.clear()
hostsList.clear()
}
override fun onServiceFound(service: NsdServiceInfo) {
@ -39,7 +64,8 @@ class NsdRepository @Inject constructor(
val resolveListener = object : NsdManager.ResolveListener {
override fun onServiceResolved(service: NsdServiceInfo) {
debug("Resolve Succeeded: $service")
hostsList?.add(service)
hostsList.add(service)
_resolvedList.value = hostsList
trySend(service)
}

View file

@ -2,6 +2,7 @@ package com.geeksville.mesh.repository.nsd
import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.nsd.NsdManager
import dagger.Module
import dagger.Provides
@ -12,9 +13,14 @@ import dagger.hilt.components.SingletonComponent
@InstallIn(SingletonComponent::class)
class NsdRepositoryModule {
companion object {
@Provides
fun provideConnectivityManager(application: Application): ConnectivityManager {
return application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
@Provides
fun provideNsdManager(application: Application): NsdManager? {
return application.getSystemService(Context.NSD_SERVICE) as NsdManager?
}
}
}
}

View file

@ -12,6 +12,7 @@ import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.nsd.NsdRepository
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.ignoreException
@ -21,6 +22,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
@ -38,7 +42,8 @@ import javax.inject.Singleton
class RadioInterfaceService @Inject constructor(
private val context: Application,
private val dispatchers: CoroutineDispatchers,
private val bluetoothRepository: BluetoothRepository,
bluetoothRepository: BluetoothRepository,
nsdRepository: NsdRepository,
private val processLifecycle: Lifecycle,
private val usbRepository: UsbRepository,
@RadioRepositoryQualifier private val prefs: SharedPreferences
@ -72,15 +77,15 @@ class RadioInterfaceService @Inject constructor(
private var isConnected = false
init {
processLifecycle.coroutineScope.launch {
bluetoothRepository.state.collect { state ->
if (state.enabled) {
startInterface()
} else if (radioIf is BluetoothInterface) {
stopInterface()
}
}
}
bluetoothRepository.state.onEach { state ->
if (state.enabled) startInterface()
else if (radioIf is BluetoothInterface) stopInterface()
}.launchIn(processLifecycle.coroutineScope)
nsdRepository.networkAvailable.onEach { state ->
if (state) startInterface()
else if (radioIf is TCPInterface) stopInterface()
}.launchIn(processLifecycle.coroutineScope)
}
companion object : Logging {
@ -174,14 +179,14 @@ class RadioInterfaceService @Inject constructor(
fun onConnect() {
if (!isConnected) {
isConnected = true
broadcastConnectionChanged(true, false)
broadcastConnectionChanged(isConnected = true, isPermanent = false)
}
}
fun onDisconnect(isPermanent: Boolean) {
if (isConnected) {
isConnected = false
broadcastConnectionChanged(false, isPermanent)
broadcastConnectionChanged(isConnected = false, isPermanent = isPermanent)
}
}

View file

@ -85,14 +85,18 @@ class TCPInterface(service: RadioInterfaceService, private val address: String)
BufferedInputStream(socket.getInputStream()).use { inputStream ->
super.connect()
while (true) try {
var timeoutCount = 0
while (timeoutCount < 180) try { // close after 90s of inactivity
val c = inputStream.read()
if (c == -1) {
warn("Got EOF on TCP stream")
break
} else
} else {
timeoutCount = 0
readChar(c.toByte())
}
} catch (ex: SocketTimeoutException) {
timeoutCount++
// Ignore and start another read
}
}