Is a way to force OkHttp to do requests over the cellular network?
My app has certain requests that must be sent over the cellular network (3G, LTE, 4G, not Wifi). I'm using Retrofit and OkHttp for all the other network requests, so I would like to use it for these requests also. It looks like ConnectivityManger.registerDefaultNetworkCallback() could help but that only supports API 21+. I would like a solution for API 19+.
Assuming API 21+,
Network network; // filled in from ConnectivityManager.NetworkCallback
OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(network.getSocketFactory())
.build();
you'll have to rebuild the OkHttpClient when the network goes away and comes back, but it's doable. You may be able to use a non-default network explicitly this way (some devices can keep the data connection up even when connected to and defaulting to WiFi).
On API 19 you may check ConnectivityManager.getActiveNetworkInfo() and refuse to operate if the type is WiFi, and check again from a BroadcastReceiver registered for android.net.conn.CONNECTIVITY_CHANGE.
Since you need a solution for lower API than 21, you can try with ConnectivityManager.
You have this method setNetworkPreference(type) where type is either ConnectivityManager.TYPE_WIFI or ConnectivityManager.TYPE_MOBILE. In your case is TYPE_MOBILE.
This method was deprecated in API 21+, you should check for current Android version.
Note that this call requires android.Manifest.permission.CHANGE_NETWORK_STATE permission.
Related
The moment I get on a wifi connection, the cellular network is completely lost even though the cellular network indicator is definitely on.
This is my network request
val request = NetworkRequest.Builder().run {
addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
build()
}
connectivityManager.registerNetworkCallback(request, callback)
I've tried looking in the connectivityManager.allNetworks list and it's no where to be found. Only the wifi network is in there.
What's even weirder is there is one other cellular network that is always there. It does not have the same ID as my cellular network. There's no connection that can be made with it. It never shows up with registerNetworkCallback. The capabilities on it always include "valid" and "internet"
What am I seeing here? Why is my cellular network lost? What is this phantom cellular network?
targetSdkVersion: 29
Device: Galaxy S10 - Android 12
I figured this out.
If you call registerNetworkCallback the above will happen, but if you call requestNetwork with TRANSPORT_CELLULAR,
connectivityManager.requestNetwork(request, callback)
Android will keep the cellular network around. I was so confused because the documentation was so lacking. Once you do that, it will ask you to add the CHANGE_NETWORK_STATE permission.
After this step, the network is available, but you won't be able to make any request with it. You have to call
connectivityManager.bindProcessToNetwork(theCellularNetwork)
to get any connection.
After this is done, the cellular network can be used in tandem with the wifi network. You can even send some traffic to one and some to the other. If you use OkHttp like I do, you just bind the client with the network's socketFactory
val client = OkHttpClient().newBuilder().run {
socketFactory(network.socketFactory)
build()
}
client.newCall(
Request.Builder().url("https://example.com").build()
).execute().let {
Log.i(TAG, "Fetched ${it.body!!.string()}")
}
The cellular network isn't lost, but your app isn't allowed to use it. Once WiFi is connected, everything is forced to use that connection. The only exception to this rule is if your phone has a feature called "Dual Acceleration", which allows the cellular connection to stay active (and obviously, the user would have to enable that feature). Alternatively, you may have a setting in your phone's Developer Options called "Cellular Data Always Active", which will do the same thing.
But needless to say, you can't rely on either of those 2 features being enabled in a production environment. So, just assume that when WiFi is connected, that's the only connection that your app can use
I have found that there are two ways to detect if the current network is metered:
With NetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) NetworkCapabilities
With ConnectivityManagerCompat.isActiveNetworkMetered ConnectivityManagerCompat
So, what's the difference between these methods? And when to use each?
First of all: ConnectivityManagerCompat.isActiveNetworkMetered is just the androidx backport of the framework's ConnectivityManager.isActiveNetworkMetered.
On platforms that have that method (API 16+), the compat version just calls the framework method directly. Otherwise, it tries to guess based on the connection type: Wi-Fi, Bluetooth, and Ethernet connections are assumed to be non-metered, while anything else is assumed to be metered. This guess isn't necessarily correct (Wi-Fi networks can be metered, for instance), but it's the best guess you can make on that API level. You should use this in the (unlikely these days) case where you need to target API 15 or below.
As far as the differences between ConnectivityManager.isActiveNetworkMetered and NetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) go: for the active data network, they are exactly the same, as the implementation simply calls that exact method.
However, because hasCapability can be called on any network, it provides you more flexibility if you want the capabilities of networks other than the active data network.
I running into an issue routing network calls using OkHttp. I'm trying to send HTTP requests to a local device (via the device's ip) using an Access Point that has no internet access. Due to some changes in Android 5.0, OkHttp will try to route the request over data instead, which then fails.
In Android 5.0's Network API, I could use the NetworkCapabilities class along with the NetworkRequest.Builder in order to tell the request to only go over Wi-Fi, However, I'm already using OkHttp and I don't want to overhaul my code. Is there anyway to do a similar thing with OkHttp?
I came up with a workaround:
final Network currentNetwork = (Network) network;
newClientBuilder.socketFactory(((Network)network).getSocketFactory())
.dns(new Dns() {
#Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
return Arrays.asList(currentNetwork.getAllByName(hostname));
}
});
We’d like to eventually include such functionality directly in OkHttp, and you might be the first volunteer/victim to test things out. What you probably want to do is use OkHttp 2.6’s new Dns interface to call through to the network of your choice. If that works, either the IP address will cause OkHttp to do the right thing anyway, or you’ll also need to replace the SocketFactory.
Is there a way to access Android's broadcast that the WiFi connection is currently a captive portal (requires web login)? Android seems to do have this built in. If not a broadcast receiver, is there a way to check for the result of the captive portal check? I believe it's using this class, which is hidden from the API:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.L_preview/android/net/CaptivePortalTracker.java
Prior to 4.2, it was probably using:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.2_r1/android/net/wifi/WifiWatchdogStateMachine.java
Background:
I had been using my own method to detect whether WiFi likely required a login. I would wait for a WiFi connected state, then ping a site and make sure there was a response. This seemed to work great in most cases. Another strategy is to do a HttpRequest and check for a redirect or the response body you receive back, similar to Android's strategy in the classes listed above.
However, new to Lollipop is that the mobile data connection is used when WiFi does not have connectivity. This means my ping method will still return results, and that a redirect would not happen, as the request would be routed over the mobile data.
Is there a way to get Android's current status of a WiFi captive portal? If not, can we make sure a request goes over WiFi even when there's no connectivity as seen by Android?
You'd have to use the new ConnectivityManager.setProcessDefaultNetwork API to force your app to communicate over the captive portal. See https://github.com/pawitp/muwifi-autologin/commit/f045fe36f1fd98a106ea652e2d56f7ddfc871760 for an example.
Complete code added:
final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
for (Network net : cm.getAllNetworks()) {
if (cm.getNetworkInfo(net).getType() == ConnectivityManager.TYPE_WIFI) {
Utils.logDebug(TAG, "Seting process network to " + net);
/*Since API 23 ConnectivityManager.setProcessDefaultNetwork(net);
is deprecated, use: */
cm.bindProcessToNetwork(net);
}
}
I am building an android app that exchanges data with our server through http api calls. In many cases users are complaining that the app is slow or doesn't work at all.
The most common cause of that is bad network connectivity (low 3g/wifi signal or congested public wifi).
What is the best way to detect bad connections? Once i can detect bad connectivity an icon or toast message can be used to inform the user about the situation.
I am using HttpUrlConnection for the api calls.
I think you can make use of ConnectivityManager. Call getActiveNetworkInfo() and then call getDetailedState() on the NetworkInfo object received. You can check the state of the connection and whether it is VERIFYING_POOR_LINK, though I don't know in which conditions this state is active.
Also you might want to listen to network changes as described in Detect Connection Changes.
I'd probably use latency. Save the time when you get the request, and when the request finishes. If you're seeing numbers that are too high, pop up the warning. If you're downloading large files, you may wish to switch to throughput (how many kbps you're transfering).
as far as i remember http is "connectionless"..
you should try concentrating on minimizing the size of your traffic.. (compress, divide.. etc)
if you really want to test connectivity i guess you should do pings.. every x seconds.. then if the ping is bad you could warn the user..