ConnectivityManager only can detect connectivity but not network availability - android

The code below is used to detect network connectivity. The scenario is the software connected to the phone's hotspot but the phone's cellular network doesn't switch on. The code below return isConnected = true, which means it is only detects connectivity? The question is how I change it to detect network availability?
fun Context.isOnline(): Boolean {
val connectivityManager =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connectivityManager.activeNetworkInfo
if (networkInfo == null) {
log(message = "Network info is **NULL**")
return false
} else {
val isConnected = when(networkInfo.detailedState) {
NetworkInfo.DetailedState.CONNECTING -> true
NetworkInfo.DetailedState.AUTHENTICATING -> true
NetworkInfo.DetailedState.OBTAINING_IPADDR -> true
NetworkInfo.DetailedState.CONNECTED -> true
NetworkInfo.DetailedState.VERIFYING_POOR_LINK -> true
else -> false
}
if (!isConnected) {
log(message = "Network state is **${networkInfo.detailedState?.name ?: "NULL"}**")
}
return isConnected
}
}

You're using a deprecated API with NetworkInfo. You should be using NetworkCapabilities.NET_CAPABILITY_VALIDATED
Indicates that connectivity on this network was successfully validated. For example, for a network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully detected.
SDK >= 23
You could change your code to something like this to see if that's true to confirm internet connectivity:
connectivityManager
.getNetworkCapabilities(connectivityManager.getActiveNetwork())
.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
SDK >= 21
If you are using anything as old as SDK 21, you can use the ConnectivityManager.registerNetworkCallback API.
boolean isConnected = false;
final NetworkRequest request =
new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
final ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkCallback networkCallback = new NetworkCallback() {
#Override
void onAvailable(Network network) {
// Triggers when this network is available.
isConnected = true;
}
#Override
void onLost(Network network) {
// Triggers when this network is lost.
isConnected = false;
}
};
connectivityManager.requestNetwork(request, networkCallback);

Related

Connectivity Manager - AllNetworks Deprecated

Can someone point me in the right direction when it comes to replacing this deprecated code for checking the internet connection on a device?
private val isNetworkAvailable = MutableStateFlow(false)
fun checkNetworkAvailability(context: Context): MutableStateFlow<Boolean> {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(this)
var isConnected = false
// allNetworks Deprecated
connectivityManager.allNetworks.forEach { network ->
val networkCapability = connectivityManager.getNetworkCapabilities(network)
networkCapability?.let {
if(it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
isConnected = true
return#forEach
}
}
}
isNetworkAvailable.value = isConnected
return isNetworkAvailable
}
You can fetch the active network and check it's currently connected or not using NetworkCapabilities
private fun isNetworkAvailable(): Boolean {
val connectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork // network is currently in a high power state for performing data transmission.
Log.d("Network", "active network $network")
network ?: return false // return false if network is null
val actNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false // return false if Network Capabilities is null
return when {
actNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> { // check if wifi is connected
Log.d("Network", "wifi connected")
true
}
actNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> { // check if mobile dats is connected
Log.d("Network", "cellular network connected")
true
}
else -> {
Log.d("Network", "internet not connected")
false
}
}
}
return false
}
Note: connectivityManager.activeNetwork requires permission android.permission.ACCESS_NETWORK_STATE

NetworkInterface.getNetworkInterfaces() returns null (android 29)

I run the following connectAndroidQ code to connect to open ap, and disconnectAndroidQ code to disconnect, but after connecting and disconnecting, the return value of NetworkInterface.getNetworkInterfaces() is null.
Before executing the code below, the return value of NetworkInterface.getNetworkInterfaces() is normal, but just running the code returns a null value.
test phone : pixel2
android version : 10
#RequiresApi(api = Build.VERSION_CODES.Q)
private boolean connectAndroidQ(#Nullable ConnectivityManager connectivityManager, ScanResult scanResult) {
if (connectivityManager == null) {
return false;
}
WifiNetworkSpecifier.Builder wifiNetworkSpecifierBuilder = new WifiNetworkSpecifier.Builder()
.setSsid(scanResult.SSID);
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(wifiNetworkSpecifierBuilder.build())
.build();
if (networkCallback != null) {
connectivityManager.unregisterNetworkCallback(networkCallback);
}
networkCallback = new ConnectivityManager.NetworkCallback() {
#Override
public void onAvailable(#NonNull Network network) {
super.onAvailable(network);
connectivityManager.bindProcessToNetwork(network);
}
#Override
public void onUnavailable() {
super.onUnavailable();
}
};
connectivityManager.requestNetwork(networkRequest, networkCallback);
return true;
}
#RequiresApi(api = Build.VERSION_CODES.Q)
private boolean disconnectAndroidQ(#NonNull final ConnectivityManager connectivityManager) {
if (networkCallback != null) {
connectivityManager.unregisterNetworkCallback(networkCallback);
networkCallback = null;
}
return true;
}
Please help.
Thanks in advance.
I solved that problem by calling bindProcessToNetwork(null).
See the link below, Note that if network ever disconnects, all Sockets created in this way will cease to work and all host name resolutions will fail. This is by design so an application doesn't accidentally use Sockets it thinks are still bound to a particular Network. To clear binding pass null for network.
https://developer.android.com/reference/android/net/ConnectivityManager#bindProcessToNetwork(android.net.Network)

How to check connectivity status of android device?

I have tried so many solutions to get to the answer but nothing helped,
I want your help.
I am developing android app which targets the minimum sdk of (4.4.4)
and when I am checking the status of the connection it's not working because of deprecated functions.
On the official site of android developers the solution is the same and I could not fix my problem.
Any help ( with code if it is possible )
Thanks.
private fun isOnLine(): Boolean {
val connectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
You can use ConnectivityManager if your app targets api 24 or later.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager = it.getSystemService(Context.CONNECTIVITY_SERVICE) as
ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(object : NetworkCallback() {
override fun onAvailable(network: Network) {
//take action when network connection is gained
}
override fun onLost(network: Network?) {
//take action when network connection is lost
}
})
}
This is good enough:
final ConnectivityManager connectivity = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
final boolean isNetworkAvailable = connectivity.getActiveNetwork() == null;

Force Android to Use Wifi network with no internet

I am building an android app that needs to communicate over a WiFi network that will not have any internet access. The problem is that even when the WiFi is connected android chooses to use cellular/mobile data when no connection internet is present on the wifi network.
I have read many posts on the issue many of which involve rooting the device but that is not possible with a production app (rooting devices is not an option). other solution (like my code bellow) suggest using bindProcessToNetwork() which works perfectly on my Sony Z2 but not on other devices I have tested on (all running 6.0.1)
private void bindToNetwork() {
final ConnectivityManager connectivityManager = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder = new NetworkRequest.Builder();
//set the transport type do WIFI
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
connectivityManager.requestNetwork(builder.build(), new ConnectivityManager.NetworkCallback() {
#Override
public void onAvailable(Network network) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(null);
if (barCodeData.getSsid().contains("ap-name")) {
connectivityManager.bindProcessToNetwork(network);
}
} else {
//This method was deprecated in API level 23
ConnectivityManager.setProcessDefaultNetwork(null);
if (barCodeData.getSsid().contains("ap-name")) {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
connectivityManager.unregisterNetworkCallback(this);
}
});
}
}
You can solve this by setting captive_portal_detection_enabled to 0 (false).
What's actually happening is that by default, everytime you connect to a wifi, the FW will test against a server (typically google) to see if it's a captive wifi (needs login). So if your wifi is not connected to google, this check will fail. After that, the device knows that wifi has no internet connection and simply will not autoconnect to it.
Setting this setting to 0 will avoid this check.
Programatically:
Settings.Global.putInt(getContentResolver(), Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 0);
Edit: You may need to use the string "captive_portal_detection_enabled" directly, instead of the constant that's not visible depending on Android version.
you'd need to disable mobile data in the Settings (not certain, if this can be done programmatically, which might be a possible option) - or take out the USIM;
else the common behavior is, that it will always fall back to the best available connection (while a connection with internet gateway might be preferred, because it is used by most application).
also see this answer.
Solution on Kotlin
class ConnectWithoutInternetTest constructor(
private val mContext: Context,
private val connectivityManager: ConnectivityManager,
private val wifiManager: WifiManager
) {
private val mWifiBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiManager.NETWORK_STATE_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO)
val isConnected = info.isConnected
val ssid: String? = normalizeAndroidWifiSsid(wifiManager.connectionInfo?.ssid)
if (isConnected) {
val builder = NetworkRequest.Builder()
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
connectivityManager.registerNetworkCallback(
builder.build(),
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val networkInfo = connectivityManager.getNetworkInfo(network)
val networkSsid = networkInfo.extraInfo
if (networkSsid == ssid) {
connectivityManager.unregisterNetworkCallback(this)
}
}
})
}
}
}
}
}
private fun init() {
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
mContext.registerReceiver(mWifiBroadcastReceiver, intentFilter)
}
private fun destroy() {
mContext.unregisterReceiver(mWifiBroadcastReceiver)
}
private fun normalizeAndroidWifiSsid(ssid: String?): String? {
return ssid?.replace("\"", "") ?: ssid
}
fun connectToWifi(ssidParam: String, password: String?) {
init()
val ssid = "\"$ssidParam\""
val config = wifiManager.configuredNetworks.find { it.SSID == ssid }
val netId = if (config != null) {
config.networkId
} else {
val wifiConfig = WifiConfiguration()
wifiConfig.SSID = ssid
password?.let { wifiConfig.preSharedKey = "\"$password\"" }
wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
wifiManager.addNetwork(wifiConfig)
}
wifiManager.disconnect()
val successful = wifiManager.enableNetwork(netId, true)
}
You're in the right path, the solution is indeed with ConnectivityManager.bindProcessToNetwork(network). This information was posted on the Android Developers Blog in this article: Connecting your App to a Wi-Fi Device.
Looking into your code this barCodeData.getSsid() doesn't look that is getting the SSID of the currently connected wifi network. If that's the case your code will never successfully bind to the network.
Try replace your if statement
if (barCodeData.getSsid().contains("ap-name"))
With
if (getNetworkSsid(context).equals("ap-name"))
Helper method in kotlin to retrieve the SSID of the connected wifi network
private fun getNetworkSsid(context: Context?): String {
// WiFiManager must use application context (not activity context) otherwise a memory leak can occur
val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
return wifiInfo.ssid.removeSurrounding("\"")
}
return ""
}
If still doesn't work please follow my complete solution where I used the same method but with some extra checks. I tested it in the Android versions 5.1.1, 6.0, 6.0.1, 7.1.1 and 8.1.0.
You're on the right path, you only need to tweak what you have a bit. One of the main issues I see is that you unregister the network callback on onAvailable() which is not a good idea as networks tend to switch between available/unavailable from time-to-time which would cause issues on your device.
The other thing to consider is requesting a network that's Wi-Fi and clearing the other network capabilities as they may cause you issues depending on your network's setup.
Here is another version of how to do that which is hopefully a bit simpler.
final NetworkRequest requestForWifi =
new NetworkRequest.Builder()
.clearCapabilities() // We only care about Wi-Fi
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
final ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkCallback networkCallbackWifi = new NetworkCallback() {
#Override
void onAvailable(Network network) {
// Once this callback triggers, a Wi-Fi network is available
WifiInfo wifiInfo = connectivity.getNetworkCapabilities(network).TransportInfo;
string ssid = wifiInfo.SSID.Trim(new char[] {'"', '\"' });
if (!ssid.contains("ap-name")) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(network);
} else {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
};
// Last thing is to actually request a network.
connectivityManager.requestNetwork(requestForWifi, networkCallbackWifi);
You can check if wifi is connected then proceed else show a dialog to user asking him to connect to a wifi network
Since the method NetworkInfo.isConnected() is now deprecated in API-23, here is a method which detects if the Wi-Fi adapter is on and also connected to an access point using WifiManager instead:
private boolean checkWifiOnAndConnected() {
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (wifiMgr.isWifiEnabled()) { // Wi-Fi adapter is ON
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
if( wifiInfo.getNetworkId() == -1 ){
return false; // Not connected to an access point
}
return true; // Connected to an access point
}
else {
return false; // Wi-Fi adapter is OFF
}
}

Send request over WiFi (without connection) even if Mobile data is ON (with connection) on Android M

I have to send UDP packets to a WiFi module (provided with own AP) with no internet connection but when I connect the mobile with the AP, Android redirects my packets on the mobile data interface because it has got internet connection.
I've used the code below to do my job but it seems not working on Android M.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setWifiInterfaceAsDefault() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest networkRequest= builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
}
I've also added
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
on my AndroidManifest.xml and I ensured myself that Settings.System.canWrite(this) returns true but still nothing.
Thanks in advance.
Stanislav's answer is correct but incomplete because only works in Lollipop.
I've wrote a complete solution for Lollipop and Marshmallow onwards for you to route all network requests through WiFi when connected to a specific network of your choice.
Kotlin
In your Activity,
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class RoutingActivity : Activity() {
private var mConnectivityManager: ConnectivityManager? = null
private var mNetworkCallback: ConnectivityManager.NetworkCallback? = null
//...
override fun onCreate(savedInstanceState: Bundle?) {
//...
routeNetworkRequestsThroughWifi("Access-Point-SSID-You-Want-To-Route-Your-Requests")
}
Route future network requests from application through WiFi (even if given WiFi network is without internet and mobile data has internet connection)
/**
* This method sets a network callback that is listening for network changes and once is
* connected to the desired WiFi network with the given SSID it will bind to that network.
*
* Note: requires android.permission.INTERNET and android.permission.CHANGE_NETWORK_STATE in
* the manifest.
*
* #param ssid The name of the WiFi network you want to route your requests
*/
private fun routeNetworkRequestsThroughWifi(ssid: String) {
mConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// ensure prior network callback is invalidated
unregisterNetworkCallback(mNetworkCallback)
// new NetworkRequest with WiFi transport type
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
// network callback to listen for network changes
mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
// on new network ready to use
override fun onAvailable(network: Network) {
if (getNetworkSsid(this#RoutingActivity).equals(ssid, ignoreCase = false)) {
releaseNetworkRoute()
createNetworkRoute(network)
} else {
releaseNetworkRoute()
}
}
}
mConnectivityManager?.requestNetwork(request, mNetworkCallback)
}
Unregister network callback
private fun unregisterNetworkCallback(networkCallback: ConnectivityManager.NetworkCallback?) {
if (networkCallback != null) {
try {
mConnectivityManager?.unregisterNetworkCallback(networkCallback)
} catch (ignore: Exception) {
} finally {
mNetworkCallback = null
}
}
}
Create network route
private fun createNetworkRoute(network: Network): Boolean? {
var processBoundToNetwork: Boolean? = false
when {
// 23 = Marshmallow
Build.VERSION.SDK_INT >= 23 -> {
processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(network)
}
// 21..22 = Lollipop
Build.VERSION.SDK_INT in 21..22 -> {
processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(network)
}
}
return processBoundToNetwork
}
 Release network route
private fun releaseNetworkRoute(): Boolean? {
var processBoundToNetwork: Boolean? = false
when {
// 23 = Marshmallow
Build.VERSION.SDK_INT >= 23 -> {
processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(null)
}
// 21..22 = Lollipop
Build.VERSION.SDK_INT in 21..22 -> {
processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(null)
}
}
return processBoundToNetwork
}
Helper
private fun getNetworkSsid(context: Context?): String {
// WiFiManager must use application context (not activity context) otherwise a memory leak can occur
val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
return wifiInfo.ssid.removeSurrounding("\"")
}
return ""
}
Bind the network using ConnectivityManager.setProcessDefaultNetwork() prevents roaming and allows for full TCP access. Thus, within the onAvailable() callback you could bind the application process to that network rather than opening a connection to a particular URL.
ConnectivityManager connection_manager =
(ConnectivityManager) activity.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder request = new NetworkRequest.Builder();
request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
connection_manager.registerNetworkCallback(request.build(), new NetworkCallback() {
#Override
public void onAvailable(Network network) {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
Original answer

Categories

Resources