Skip to content

Instantly share code, notes, and snippets.

@paulo-raca
Created July 30, 2021 12:08
Show Gist options
  • Save paulo-raca/23d3ef2795bbb64c24c0776dcb66cfe7 to your computer and use it in GitHub Desktop.
Save paulo-raca/23d3ef2795bbb64c24c0776dcb66cfe7 to your computer and use it in GitHub Desktop.
Handy NetworkCallback implementation that merges the bunch of distinct callbacks in a single state
package com.crowdstrike.android.vpn.util
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.util.Log
/**
* NetworkCallback is quite inconvenient in that data is split between
* Network, NetworkCapabilities, LinkProperties and Blocked,
* and we get part of the state on each callback.
*
* To put everything together, a clumsy and stateful callback is necessary.
* This class takes care of the boring stateful bits and puts it all together in a `NetworkState`
*/
open class NetworkStateCallback {
companion object {
val TAG = NetworkStateCallback::class.java.simpleName
}
val networkCallback : ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {
inner class InternalNetworkState(val network: Network) {
var networkCapabilities: NetworkCapabilities? = null
var linkProperties: LinkProperties? = null
var blocked: Boolean? = null
var suspended: Boolean = false
val isDataComplete: Boolean
get() = (networkCapabilities != null && linkProperties != null && blocked != null)
val networkState: NetworkState
get() = NetworkState(network, networkCapabilities!!, linkProperties!!, blocked!!, suspended)
var isChange = false
}
val networks = HashMap<Network, InternalNetworkState>()
private fun getInternalState(network: Network): InternalNetworkState {
return networks.computeIfAbsent(network, {network -> InternalNetworkState(network)})
}
private fun networkStateUpdated(networkState: InternalNetworkState) {
// The callback received a bunch of initial events at once to assemble all the data.
// This callback stores the data internally, but only propagates it once it is complete
if (!networkState.isDataComplete) {
return
}
this@NetworkStateCallback.onAvailable(networkState.networkState, networkState.isChange)
networkState.isChange = true
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
val networkState = getInternalState(network)
networkState.networkCapabilities = networkCapabilities
networkStateUpdated(networkState)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
val networkState = getInternalState(network)
networkState.linkProperties = linkProperties
networkStateUpdated(networkState)
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
val networkState = getInternalState(network)
networkState.blocked = blocked
networkStateUpdated(networkState)
}
// onNetworkSuspended / onNetworkResumed are marked with @hide in AOSP,
// so the compiler doesn't know these are overrides
/*override*/ fun onNetworkSuspended(network: Network) {
val networkState = getInternalState(network)
networkState.suspended = true
networkStateUpdated(networkState)
}
/*override*/ fun onNetworkResumed(network: Network) {
val networkState = getInternalState(network)
networkState.suspended = false
networkStateUpdated(networkState)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
val networkState = networks[network]
if (networkState != null && networkState.isDataComplete) {
this@NetworkStateCallback.onLosing(networkState.networkState)
}
}
override fun onLost(network: Network) {
val networkState = networks.remove(network)
if (networkState != null && networkState.isDataComplete) {
this@NetworkStateCallback.onLost(networkState.networkState)
}
}
override fun onUnavailable() {
this@NetworkStateCallback.onUnavailable()
}
}
open fun onAvailable(network: NetworkState, change: Boolean) {
Log.v(TAG, "Network available: $network, change=$change")
}
open fun onLosing(network: NetworkState) {
Log.v(TAG, "Losing Network: $network")
}
open fun onLost(network: NetworkState) {
Log.v(TAG, "Lost Network: $network")
}
open fun onUnavailable() {
Log.v(TAG, "Network unavailable")
}
}
data class NetworkState (
val network: Network,
val networkCapabilities: NetworkCapabilities,
val linkProperties: LinkProperties,
val blocked: Boolean,
val suspended: Boolean
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment