-
-
Save Farbklex/f84029889444ee9c52a331a7e2bd10d2 to your computer and use it in GitHub Desktop.
package me.a_hoffmann.gists | |
import android.content.Context | |
import android.net.ConnectivityManager | |
import android.net.NetworkCapabilities | |
/** | |
* Checks if the device has an internet connection. | |
* NOTE: Works only on android API level 23 and above! | |
*/ | |
class ConnectivityChecker(private val context: Context){ | |
/** | |
* Check if the device has an internet connection. | |
* | |
* @return True if the device is connected to a network which also gives it access to the internet. | |
* False otherwise. | |
*/ | |
fun isInternetAvailable(): Boolean { | |
val connectivityManager = context.getSystemService( | |
Context.CONNECTIVITY_SERVICE) as ConnectivityManager? | |
?: return false | |
val activeNetwork = connectivityManager.activeNetwork ?: return false | |
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false | |
// If we check only for "NET_CAPABILITY_INTERNET", we get "true" if we are connected to a wifi | |
// which has no access to the internet. "NET_CAPABILITY_VALIDATED" also verifies that we | |
// are online | |
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) | |
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) | |
} | |
} |
Sadly
NET_CAPABILITY_INTERNET
andNET_CAPABILITY_VALIDATED
are unreliable when using local VPNs like:* NetGuard (https://f-droid.org/en/packages/eu.faircode.netguard/) * AdGuard (https://adguard.com/en/adguard-android/overview.html)
I have noticed this behaviour, but can't find many other references to it online! Frustrating.
I am also using WorkManager
, though the behaviour I have observed is that it behaves the same as checking for NET_CAPABILITY_INTERNET
and NET_CAPABILITY_VALIDATED
(i.e. that the VPN makes it think there's an internet connection even if there isn't one). So I put another internet check inside the Work
using the deprecated ConnectivityManager#getActiveNetworkInfo()
and retry()
if none is found. Seems to work, but frustrating that I needed to do some many work arounds and use deprecated APIs to get it to work.
private boolean hasNetworkCapability(NetworkCapabilities networkCapabilities) {
boolean hasCapability = false;
/*
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // API >= 21
&& networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) // API >= 23
&& (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) // API >= 28
|| networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)); // API >= 28
} else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // API >= 21
&& networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); // API >= 23
} else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); // API >= 21
}
return hasCapability;
*/
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
hasCapability = hasCapability || networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
return hasCapability;
}
Here is what I wrote as a util
suspend fun isDnsResolvable(): Boolean {
return withContext(Dispatchers.IO) {
try {
/*
www.msftconnecttest.com is what windows uses to determine if a network is connected to the internet
to be fair, windows requests for http://www.msftconnecttest.com/connecttest.txt and checks the content
but a simple DNS lookup is enough for us, me thinks :^)
*/
val address = InetAddress.getByName("www.msftconnecttest.com")
!address.hostAddress.isNullOrEmpty()
} catch (e: UnknownHostException) {
false
}
}
}
suspend fun hasNetwork(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false
return activeNetwork.hasCapability(NET_CAPABILITY_VALIDATED) && isDnsResolvable()
}
I recommend using kotlinx.coroutines.flow.flow
to check at an interval. (The network listener is not reliable)
var connectedToInternet = false;
flow {
while (true) {
if (connectedToInternet) {
delay(4.seconds)
} else {
delay(1.seconds)
}
connectedToInternet = hasNetwork(this@MainActivity)
delay(1.seconds)
emit(Unit)
}
}.launchIn(scope)
Taken from here.
You might want to use this for notifying the user about the network connectivity just like YouTube and TikTok do
private boolean hasNetworkCapability(NetworkCapabilities networkCapabilities) { boolean hasCapability = false; /* if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // API >= 21 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) // API >= 23 && (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) // API >= 28 || networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)); // API >= 28
don't we need both network not suspended as well as foreground network is available for use by apps?
} else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // API >= 21 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); // API >= 23 } else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); // API >= 21 } return hasCapability; */ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { hasCapability = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { hasCapability = hasCapability || networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } } return hasCapability; }
And I no longer check if an address can be resolved using DNS. There is a risk that the IP address is resolved by the internal DNS cache.