How to use Android's DownloadManager over P2P connection - android

From an Android application, I would like to use DownloadManager to download files from a peer which is connected using P2P (Wi-Fi Direct).
However, I found that unless the tablet/phone is connected to a WiFi network, DownloadManager will refuse to download over the P2P connection. It will output an error claiming "NO CONNECTION". If both Wi-Fi and P2P are connected, I can successfully download over the P2P connection.
Here's a sample code:
String url = "http://ipv4.download.thinkbroadband.com/5MB.zip";
String localFile = "5MB.zip";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
request.setTitle("test.txt");
request.setDescription("Testing DownloadManager -- 5MB.zip");
request.setVisibleInDownloadsUi(true);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setDestinationInExternalFilesDir(context, null, localFile);
DownloadManager downloadManager = (DownloadManager)context.getSystemService(context.DOWNLOAD_SERVICE);
long downloadId = downloadManager.enqueue(request);
Now, I'm not sure if I'm just not using it correctly, or whether it's an unsupported feature. I'm willing to change Android's codebase if needed in order to get it to work (but I can't choose an alternative to DownloadManager).
Inspecting Android's code, it seems that the method checkCanUseNetwork() will return "NO_CONNECTION" because it gets a null NetworkInfo from mSystemFacade. See:
/packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadInfo.java
Further investigation hints to a "problem" in ConnectivityService. While it sets its mActiveDefaultNetwork to the correct type when Wi-Fi network is connected, it won't do anything when P2P is connected.
/frameworks/base/services/core/java/com/android/server/ConnectivityService.java
I'm using Nexus 4 with Android 5.0.1, but I've seen the same issue with KitKat 4.4.

If you remove following line of code, it will work.
"request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);"

Related

HttpURLConnection.connect() fails after network reconnection on Android 10

My app connects to an external device using it's WiFi (the device works as a server). With introduction of Android 10 I needed to implement separate WiFi connectivity flow for different plaftorms (WifiNetworkSpecifier for Android 10+ and wifiManager.enableNetwork for < Android 10). The connectivity flow itself works fine, but I have some problems with stream communication.
In the app I have the ability to upload files to that external device. To do that I need to use HttpURLConnection. So I run:
val url = URL(UPDATE_FIRMWARE_URL)
val connection = (url.openConnection() as HttpURLConnection)
with(connection) {
doInput = true
doOutput = true
useCaches = false
requestMethod = METHOD_POST
//setRequestProperty(HEADER_CONNECTION, "Keep-Alive")
setRequestProperty("Connection", "close")
connectTimeout = 6000
setRequestProperty(HEADER_USER_AGENT, "Android Multipart HTTP Client 1.0")
setRequestProperty(HEADER_CONTENT_TYPE, "multipart/form-data; boundary=$boundary")
}
connection.connect()
val outputStream = connection.outputStream
DataOutputStream(outputStream).use { outputStream ->
// actual file upload
}
Now, the actual update consists of two files, and after first upload the device restarts, and I need to reconnect to it's wifi and upload the second file.
On Android < 9 the entire upload flow (with two files) works fine but on Android 10, after I send the first file and reconnect to the device's WiFi, when I call connection.connect() I get ConnectExcpetion with internal cause connect failed: ENETUNREACH (Network is unreachable) (which really makes no sense, cause I'm connected to that network...)
java.net.ConnectException: Failed to connect to (...)
at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:1409)
at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:1359)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:221)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:483)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:135)
Initially I had a problem also with connecting for the first time on Android 10, but I found this article, and adding the connectTimeout helped, but now the connection still fails when I try to connect for the second (and every next) time. The only thing that helps is restaring the entire app (which is no real solution).
What may be the problem, that the next connections fail despite I always execute the same code?
After a few days I finally found an answer to my question. It turns out that on Android 10 when you connect to the Access Point that does not offer the internet (eg. my external device) the standard API calls (using Retrofit) works fine, but when trying to use HttpURLConnection the system tries to use some network with internet connection, and as there is none, the connection fails.
The only way for the connection to work is to force the system to use our network by using ConnectivityManager.bindProcessToNetwork(network). This solution was proposed here and I've got no idea why someone downvoted that answer. It's correct.
What's interesting is that if we connect to the no-internet network via device settings, the connection works just fine even without binding.

Android Download Manager "Download Unsuccessful"

This is my first time trying to implement DownloadManager and no matter what I try, I always get a notification saying "Download unsuccessful." I've looked at many other SO forums, a few tutorials, and what I have should work. Yes, I've set internet and external storage permissions in the manifest file. And yes, I've given storage permission in the app settings on the phone. I've tried this on both an Android emulator running API 28 and a real phone running the same. Here is the code I have:
String url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4";
DownloadManager downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle("title");
request.setDescription("Your file is downloading");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, "" + System.currentTimeMillis());
request.allowScanningByMediaScanner();
request.setAllowedOverMetered(true);
request.setAllowedOverRoaming(true);
//Enqueue download and save the referenceId
long downloadReference = downloadManager.enqueue(request);
if (downloadReference != 0) {
Toast.makeText(getApplicationContext(), "download started", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(getApplicationContext(), "no download started", Toast.LENGTH_SHORT).show();
}
Any help or suggestions are appreciated. Thanks.
This issue occur due to network security. If You are using un-secure url in above pie API, then it can't execute your url. Check Official Documentation.
Reason for avoiding cleartext traffic is the lack of confidentiality,
authenticity, and protections against tampering; a network attacker
can eavesdrop on transmitted data and also modify it without being
detected.
Add following in manifest to bypass all security.
<application
android:name=".ApplicationClass"
....
android:usesCleartextTraffic="true">
My Experience on 1/11/2021, min SDK 19, Target SDK 30
I spent a day on using Download Service and finally it worked.
To sum it up for anyone who wants to try for the first time:
Dont't forget to add WRITE_EXTERNAL_STORAGE and INTERNET permissions in Manifest.
use requestPermissions() to grant permission from user.
use getExternalFilesDir() instead of getExternalStorageDirectory().
If you're downloading from http:// so add usesCleartextTraffic="true" to manifest.

Use multiple network interfaces in an app

I wrote an app that is triggering a Sony qx smartphone attachable camera over wifi. However I need to transfer the images off the phone over another local network in real time. Since the wifi card is being used for qx connection I need to be able to use ethernet over usb for transferring images off the phone. Http requests will be used to trigger the camera and send the images off the phone.
Is it possible in one android app on a phone with two network interfaces setup to specify for certain http requests to use one network interface and for others to use another network interface ? Does this need to be done through routing tables, not java?
The phone I'm using is a rooted nexus 6p.
Update:
Currently, I was able to get an Ethernet adapter working with the device (Nexus 6P). The device is connected to a local network over Ethernet. When the Wi-Fi interface is off, I can ping all devices on the local network the device is connected to over Ethernet. However, I am unable to access the web servers (Not using DNS) of any of the devices on that network (which I know they are running), i.e. Http via a browser app. The nexus 6p is connected to the network over Ethernet via a Ubiquiti Station. This seems to be a routing issue.
I can tether(usb interface) and use Wi-Fi in one app, so that leads me to believe it is possible to use Ethernet and Wi-Fi.
Update2:
After more testing, it seems to be that it is a permissions issue. Since when I ping the network the device is connected to over Ethernet without first running su in the terminal the network doesn't exist. However, when I run su then ping, I can ping the network. Thus it seems my app needs to get superuser permission before accessing Ethernet. I've granted it superuser access, but nothing has changed. I read that simply running su isn't enough from one of the comments in this post. This is because su just spawns a root shell that dies. This also explains why I couldn't access any of the web servers on this network via a browser app. Is it possible to grant my app access to the Ethernet interface when making HTTP calls like give HttpURLConnection root access, if that makes any sense (running su doesn't work)? There seems to definitely be a solution since HttpURLConnection can make calls over the USB tethering interface (Nexus 6P calls it rndis0) fine.
Update 3:
I found online here , that I can make my app a System app (thought this might grant the app eth0 access). I just moved my app to /system/app and then rebooted. However, this didn't seem to give the app anymore privileges (thus not solving the problem) , or there is something else required to make the app system than just copying it to /system/app.
Update 4:
So I was able to get Ethernet working on every app without root permissions! It seemed to be that it only works over DHCP and does not like static connections, which I was using. It works with Wi-Fi enabled, however, I cannot contact any of the devices on the Wi-Fi network when Ethernet is enabled. Is there a way around this? Does it have to do with setting two default gateways?
Since you were programming in Nexus 6P, you can try to use the new API added in ConnectivityManager to select the ethernet as your preferred network connection for your process.
Since I can't build the similar environment like yours, I am not sure if it works. It's just a suggested solution, totally not tested and verified.
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
Network etherNetwork = null;
for (Network network : connectivityManager.getAllNetworks()) {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) {
etherNetwork = network;
}
}
Network boundNetwork = connectivityManager.getBoundNetworkForProcess();
if (boundNetwork != null) {
NetworkInfo boundNetworkInfo = connectivityManager.getNetworkInfo(boundNetwork);
if (boundNetworkInfo.getType() != ConnectivityManager.TYPE_ETHERNET) {
if (etherNetwork != null) {
connectivityManager.bindProcessToNetwork(etherNetwork);
}
}
}
Just to give a little more explanation on how this finally got solved.
Utilizing #alijandro's answer I was able to switch back and forth between Ethernet and Wi-Fi in one app. For some reason for the Ethernet to work it required the network gateway to supply DHCP address, not static. Then since the bindProcessToNetwork, used in #alijandro's answer is per-process, I decided to split communications with the QX camera into a Service that runs in a separate Process. The main Application (another process) would post images over Ethernet to a local network. I was successfully able to contact the devices on the local network via HTTP over Ethernet while simultaneously triggering the QX over Wi-Fi. Currently, I used Messenger to communicate using IPC to tell the QX triggering Service what methods to call.
Most of android tv boxes can use wifi and ethernet together. In my device, i can enable ethernet from this path ---
Settings -> More ... > Ethernet ---
But your device wont have a menu like that as i understand. So you should make an app to do that. This application needs to access some system specific resources so your device needs to be rooted or application needs to signed with system signature.
Also this topic can help you link
There is an easy way to do this that will answer the OP's original question about how to do this with a single application (not two separate app processes) using ConnectivityManager.requestNetwork().
The docs for ConnectivityManager.requestNetwork() allude to this:
... For example, an application could use this method to obtain a
connected cellular network even if the device currently has a data
connection over Ethernet. This may cause the cellular radio to consume
additional power. Or, an application could inform the system that it
wants a network supporting sending MMSes and have the system let it
know about the currently best MMS-supporting network through the
provided NetworkCallback. ...
For OP's scenario of using Wi-Fi for some traffic and ethernet for other traffic one only needs to call ConnectivityManager.requestNetwork() twice with two separate requests. One for TRANSPORT_WIFI and one for TRANSPORT_ETHERNET. The operative item here is we need a way to uniquely identify these networks. For OP's scenario, we can use transport type.
final NetworkRequest requestForWifi =
new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
final NetworkRequest requestForEthernet =
new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build();
final ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkCallback networkCallbackWifi = new NetworkCallback() {
#Override
void onAvailable(Network network) {
// Triggers when this network is available so you can bind to it.
}
#Override
void onLost(Network network) {
// Triggers when this network is lost.
}
};
final NetworkCallback networkCallbackEthernet = new NetworkCallback() {
#Override
void onAvailable(Network network) {
// Triggers when this network is available so you can bind to it.
}
#Override
void onLost(Network network) {
// Triggers when this network is lost.
}
};
connectivityManager.requestNetwork(requestForWifi, networkCallbackWifi);
connectivityManager.requestNetwork(requestForEthernet, networkCallbackEthernet);
Then, once the callbacks trigger, you can then in the pertinent code (e.g. OP's code for transferring images), listen for onAvailable(Network network) and use the provided Network with Network.OpenConnection() to connect to an HTTP server using that network.
This would allow you to connect to two separate Networks from the same application.

How to detect if system is connected to ad hoc or infrastructure wifi?

I am working on an application that checks, before enabling a download, that the connection is reliable (basically the connection should be an infrastructure wifi and not data pack) But in case the user is either
using an ad hoc network, or
mobile device's internet connection as WAP
and then connecting and starting the download on desktop, it is still undesired. Is there a way to detect if some wifi connection is actually not from ad hoc or using phone's WAP?
You can detect...
...the Ad-Hoc Network by checking if you actually can access a server on the internet - usually, Ad-Hoc does not include fully connectivity so if you have network but no internet access, a download won't work
...the usage of a phone access point by measuring the round-trip time of a request - they are usually quite high on mobile broadband.
long start = System.nanoTime();
HttpGet requestForTest = new HttpGet("http://m.google.com");
try {
new DefaultHttpClient().execute(requestForTest); // can last...
}
catch (Exception e) {
}
long rtt = System.nanoTime() - start;
// ... evaluate the rtt
This table may be relevant for the evaluation (source)
Generation | Data rate | Latency
2G | 100–400 Kbit/s | 300–1000 ms
3G | 0.5–5 Mbit/s | 100–500 ms
4G | 1–50 Mbit/s | < 100 ms
Apart from those two options: why do you specifically ban Ad-Hoc or mobile broadband? Shouldn't you either
ban nothing and let the user decide if they want to wait for ages
or
ban ALL slow connections, i.e. monitoring the transfer rate for a couple of seconds and cancel the download automatically if it is too small
Essentially, you're writing an Application on layer 7 of the ISO/OSI model and you want to know contents of layer 2. That is explicitly not how it should work.
If your download is big, you should protect the user from burning up their mobile contract's data volume and notify the user before executing the download.
That is also how the Google Play store handles downloads bigger than a few MBytes.
you could have an option in the preferences of your app to only allow downloads when on Wi-Fi so users can be sure to save their data plan.
If you absolutely don't want a user to download your files over mobile network, only allow Wi-Fi and then do a pind and a download of a 1MB file to measure the bandwidth before deciding if the actual download will take forever or not.
All the rest should be handled by the system and doesn't need to concern you.
to check if its connected to wifi:
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifi.isConnected()) {
// is connected to wifi
}
don't forget to add permission to Manifest:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>

Android DownloadManager 'Download Requires Network' error

I'm using the DownloadManager to queue up some downloads I'm making but running into this issue when I specifically try to use the Mobile/4G connection. I'm using an Android 2.3.4 phone. My code is using the 2.3.3 API.
I'm doing the following command (I want to force the connection to use 4G/3G)
request.setAllowedNetworkTypes(Request.NETWORK_MOBILE);
Whenever it attempts to download however, it places the download in the DownloadManager listing but it forever remains in the status "In progress" and an error at the top lists the file name and the error "Download requires network."
When I investigate further and connected my device to see the logs in logcat, I see the following error when it attempts to download:
Aborting request for download 92: download was requested to not use the current network type
I have the following permissions:
android.permission.WRITE_OWNER_DATA
android.permission.READ_OWNER_DATA
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_WIFI_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_STATE
Any ideas of what it could be? Am I still missing a permission? Is there another setting that I need to control to specify the use of the Network connection only?
EDIT:
I have tried this on a brand new Galaxy tablet and this is the behavior I notice using this device: When the wifi is on and connected, it fails to download when specifying to use the NETWORK_MOBILE. If the wifi is turned off or not connected, it has no problem using the 4G connection. I'm thinking this is a security feature being done by the device, can this be overidden?
I dont know how you are setting up your download manager but this is how ive used it in the pass.
private long enqueue;
private DownloadManager dm;
public void onClick(View view) {
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
Request request = new Request(
Uri.parse("File URL"));
enqueue = dm.enqueue(request);
}
public void showDownload(View view) {
Intent i = new Intent();
i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS);
startActivity(i);
}
This error occurs when you have no data connection (doesn't matter whether or not you are connected to wifi). You are allowed to specify NETWORK_MOBILE by itself, but it will throw you an error saying "Download Requires Network" if it tries to do the download when your data connection isn't currently working.
My advice to get a data connection again is to walk around until you get a connection again. I can tell when I have a data connection on my Droid Bionic phone because the top bar icons that say 4G LTE and the 4 bars turn blue instead of white.

Categories

Resources