Network monitor : try to make it more reliable...

This commit is contained in:
ganfra 2023-06-23 16:48:31 +02:00
parent 3176cad7e2
commit 1185dbd276

View file

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
@file:OptIn(FlowPreview::class)
package io.element.android.features.networkmonitor.impl package io.element.android.features.networkmonitor.impl
import android.content.Context import android.content.Context
@ -27,62 +29,69 @@ import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.stateIn
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ContributesBinding(scope = AppScope::class) @ContributesBinding(scope = AppScope::class)
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
class NetworkMonitorImpl @Inject constructor( class NetworkMonitorImpl @Inject constructor(
@ApplicationContext context: Context @ApplicationContext context: Context,
appCoroutineScope: CoroutineScope,
) : NetworkMonitor { ) : NetworkMonitor {
private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java) private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java)
private val callback = object : ConnectivityManager.NetworkCallback() { override val connectivity: StateFlow<NetworkStatus> = callbackFlow {
override fun onAvailable(network: Network) {
_connectivity.value = connectivityManager.currentConnectionStatus() /**
Timber.v("Connectivity status (available): ${connectivityManager.currentConnectionStatus()}") * Assume there is no network available as soon as onLost is called.
* Reading activeNetwork synchronously from the callback is not safe.
* If there is an other active network we'll get some callback in onCapabilitiesChanged.
* Debounce the result to avoid quick offline<->online changes.
*/
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network) {
trySendBlocking(NetworkStatus.Offline)
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
trySendBlocking(networkCapabilities.getNetworkStatus())
}
} }
trySendBlocking(connectivityManager.activeNetworkStatus())
override fun onLost(network: Network) {
_connectivity.value = connectivityManager.currentConnectionStatus()
Timber.v("Connectivity status (lost): ${connectivityManager.currentConnectionStatus()}")
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
_connectivity.value = connectivityManager.currentConnectionStatus()
Timber.v("Connectivity status (changed): ${connectivityManager.currentConnectionStatus()}")
}
}
private val _connectivity = MutableStateFlow(NetworkStatus.Online)
override val connectivity: StateFlow<NetworkStatus> = _connectivity
init {
listenToConnectionChanges()
}
private fun listenToConnectionChanges() {
val request = NetworkRequest.Builder() val request = NetworkRequest.Builder()
// .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
// .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build() .build()
connectivityManager.registerNetworkCallback(request, callback) connectivityManager.registerNetworkCallback(request, callback)
Timber.d("Subscribe")
awaitClose {
Timber.d("Unsubscribe")
connectivityManager.unregisterNetworkCallback(callback)
}
}
.debounce(300)
.stateIn(appCoroutineScope, SharingStarted.WhileSubscribed(), connectivityManager.activeNetworkStatus())
_connectivity.tryEmit(connectivityManager.currentConnectionStatus()) private fun ConnectivityManager.activeNetworkStatus(): NetworkStatus {
return activeNetwork?.let {
getNetworkCapabilities(it)?.getNetworkStatus()
} ?: NetworkStatus.Offline
} }
private fun ConnectivityManager.currentConnectionStatus(): NetworkStatus { private fun NetworkCapabilities.getNetworkStatus(): NetworkStatus {
val hasInternet = activeNetwork?.let(::getNetworkCapabilities) val hasInternet = hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
return if (hasInternet) { return if (hasInternet) {
NetworkStatus.Online NetworkStatus.Online
} else { } else {