From 2d30fd89bc90f6026176a593af215ea05399648b Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 20 Jul 2023 18:42:50 -0300 Subject: [PATCH] feat: add network availability monitoring --- .../com/geeksville/mesh/model/BTScanModel.kt | 2 +- .../mesh/repository/nsd/NsdRepository.kt | 36 ++++++++++++++++--- .../repository/nsd/NsdRepositoryModule.kt | 8 ++++- .../repository/radio/RadioInterfaceService.kt | 29 ++++++++------- .../mesh/repository/radio/TCPInterface.kt | 8 +++-- 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index 51759b722..e00d9cea3 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -210,7 +210,7 @@ class BTScanModel @Inject constructor( } // Include Network Service Discovery - nsdRepository.resolvedList?.forEach { service -> + nsdRepository.resolvedList.value.forEach { service -> addDevice(TCPDeviceListEntry(service)) } diff --git a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt index c65f80ca8..794a1bd01 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt @@ -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, + private val connectivityManager: dagger.Lazy, ) : Logging { - private val resolveQueue = Semaphore(1) - private var hostsList: ArrayList? = null + private val availableNetworks: HashSet = HashSet() + val networkAvailable: Flow = callbackFlow { + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + availableNetworks.add(network) + trySend(availableNetworks.isNotEmpty()).isSuccess + } - val resolvedList: List? 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() + + private val _resolvedList = MutableStateFlow>(emptyList()) + val resolvedList: StateFlow> get() = _resolvedList private val _networkDiscovery: Flow = 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) } diff --git a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepositoryModule.kt b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepositoryModule.kt index 2202b2984..6c06d1be5 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepositoryModule.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepositoryModule.kt @@ -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? } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index e91ba166b..94b95b1da 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -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) } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt index f7eeb22f3..9e0032045 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt @@ -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 } }