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.
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 need to connect 20+ android devices in a client-server network. Each client Android device will be communicating with the server Android device and vice versa. The client devices do not need to communicate with each other.
The server device would need access to internet for a brief period while connected to the clients.
My question is, can Wi-Fi P2P support that many connections reliably? And if yes, how do I go about implementing them?
Or will I have to ensure that all devices are on the same WLAN?
From experience, in a real-world deployment of an Android Wi-Fi Direct application, 20 devices should not be an issue.
Theoretically, the maximum number of devices in a Wi-Fi P2P group, where the GO is an Android device, is 254. The group owner is assigned the IP, 192.168.49.1. Clients are assigned an IP from the range, 192.168.49.2 to 192.168.49.254.
The group owner address is defined by the following in WifiP2pServiceImpl.java:
/* Is chosen as a unique address to avoid conflict with
the ranges defined in Tethering.java */
private static final String SERVER_ADDRESS = "192.168.49.1";
Determining the range for the clients is done as follows:
In WifiP2pServiceImpl.java, the startDhcpServer(String intf) method will start the DHCP server for a given interface - not a surprise. This method is called when the group has started and the device is the group owner.
Taking a closer look at this code, we can see that on the InterfaceConfiguration object, the link address is set to 192.168.49.1 and the prefix length is 24 (prefix length is the number of bits set in a subnet mask, here equivalent to 255.255.255.0) - this implies the answer, but we can dig a little further.
ifcg = mNwService.getInterfaceConfig(intf);
ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
SERVER_ADDRESS), 24));
ifcg.setInterfaceUp();
mNwService.setInterfaceConfig(intf, ifcg);
Next, the following commands will restart tethering with the DHCP range specified by the String[], tetheringDhcpRanges. The calls of mNwService (Network Management Service) methods will execute the appropriate netd commands.
ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
String[] tetheringDhcpRanges = cm.getTetheredDhcpRanges();
if (mNwService.isTetheringStarted()) {
if (DBG) logd("Stop existing tethering and restart it");
mNwService.stopTethering();
}
mNwService.tetherInterface(intf);
mNwService.startTethering(tetheringDhcpRanges);
And cm.getTetheredDhcpRanges() is ultimately a reference to the following (ConnectivityManager.getTetheredDhcpRanges() -> ConnectivityService.getTetheredDhcpRanges() -> Tethering.getTetheredDhcpRanges()):
// USB is 192.168.42.1 and 255.255.255.0
// Wifi is 192.168.43.1 and 255.255.255.0
// BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
// with 255.255.255.0
// P2P is 192.168.49.1 and 255.255.255.0
private String[] mDhcpRange;
private static final String[] DHCP_DEFAULT_RANGE = {
"192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
"192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
"192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
"192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
}
and:
mDhcpRange = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_dhcp_range);
if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
mDhcpRange = DHCP_DEFAULT_RANGE;
}
in com.android.server.connectivity.Tethering.
Of course, it is possible for the device manufacturer to change this code, so this is also worth considering.
For those planning to deploy applications where there will be many users, a mechanism to allow a more than one device to be GO is required. If data needs to be synchronised between devices, it is simple to simulate "churn" and have GOs only be a GO for a time period before becoming a client to another GO and synchronising any data.
The max number as far as I know is not specified, so you would need to test that out to be certain. Also there could be differences between hardware.
Anyway, the basic implementation would be rather simple. The server would call GreateGroup, so it would be the Groupowner in all cases. And then start locals service advertising. Clients then would simply look for the advertisement and once they see it, they would start connection process to the server. One the server connection is made over Wifi direct you would simply start socket communications from the client to the server (server would have listening socket on all times).
Note that connection would require user to click on the dialog showed when client tries to connect to the group owner. And if you want to get rid of this. Then you could actually use the Accesspoint created by GreateGroup, and add the access point name as well as the password to the advertising. Then your clients could actually use the accesspoint to connect (like to any Wlan accesspoint)
Note though that the Wifi Direct way, would not interfere with Wifi connections, not would it require it. But the accesspoint way would mean that any existing Wifi connection from the client would be disconnected, and the device thinks that the connection made to the server would provide normal internet connectivity.
Remember that devices don't need to be connected to a network to connect to each other. Wi-Fi Direct allows them to connect directly.
Here is a list of Wi-Fi Direct resources that you may find useful: https://groups.google.com/forum/#!topic/wi-fi-direct/uWpuOzHY6y0
I'd recommend following Android's Service Discovery Demo and try implementing it yourself. And here is the source code for the demo.
I noticed that while streaming audio from a remote server through 3G (mobile) connection and while the WIFI is disconnected or OFF, as soon as WIFI is activated and connected, connection through 3G is dropped.
I want the app keep using 3G even if WIFI is connected too now. I want to do this to keep continuity. (User may opt-in/out to/from this behaviour).
Is there a special flag, lock, etc.. For this purpose?
This isn't possible on devices before Android 5.0 (Lollipop). The OS only keeps one interface up at a time, and applications don't have any control over this choice.
On devices running Android 5.0 or newer, you can use the new multi-networking APIs to pick which interface you want to use for network traffic.
Here's the steps to do this, from the Android 5.0 changelog:
To select and connect to a network dynamically from your app, follow these steps:
Create a ConnectivityManager.
Use the NetworkRequest.Builder class to create an NetworkRequest object and specify the network features and transport type your app is interested in.
To scan for suitable networks, call requestNetwork() or registerNetworkCallback(), and pass in the NetworkRequest object and an implementation of ConnectivityManager.NetworkCallback. Use the requestNetwork() method if you want to actively switch to a suitable network once it’s detected; to receive only notifications for scanned networks without actively switching, use the registerNetworkCallback() method instead.
When the system detects a suitable network, it connects to the network and invokes the onAvailable() callback. You can use the Network object from the callback to get additional information about the network, or to direct traffic to use the selected network.
Specifically, if you want to force your traffic over 3G/LTE, even if there's a WiFi signal present, you'd use something like this:
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder req = new NetworkRequest.Builder();
req.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
cm.requestNetwork(req.build(), new ConnectivityManager.NetworkCallback() {
#Override
public void onAvailable(Network network) {
// If you want to use a raw socket...
network.bindSocket(...);
// Or if you want a managed URL connection...
URLConnection conn = network.openConnection(...);
}
// Be sure to override other options in NetworkCallback() too...
}
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 want to write automated tests to run on the emulator that verify that my app behaves correctly when the network connection is spontaneously dropped. To do so, I'd like to write tests like the following:
Enable network connection.
Start some app network operation such as a long file download.
Disable network connection.
Verify that lost network connection dialog is shown.
I have seen various stackoverflow answers that address this by suggesting manually disabling the network connection from the host PC via the command line. This cannot be not automated by a test that is running on the emulator, because the emulator has no access to its host PC. And running the commands in a host PC shell script would be very difficult to attempt to automatically synchronize with an emulator test.
Alternately I've seen answers that suggest manipulating the emulator's network settings, which apparently don't actually disable access, they only cause explicit connectivity checks like networkInfo.isConnected() to report a disconnected state. They still allow bidirectional communication. (E.g., when emulator network access is disabled, the browser is still fully functional.)
Has anyone been able to construct automated tests that run on the emulator and actually turn on and off network access as needed mid-test?
There is presently no way to truly disable network access programmatically from within a unit test. I filed an enhancement request for this issue.
If you're not averse to invoking an undocumented method, you can do the following to turn off / on the data connection.
public void enableData(Context context, boolean enable) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
Method m = cm.getClass().getDeclaredMethod("setMobileDataEnabled", boolean.class);
m.invoke(cm, enable);
} catch (Exception e) {
}
}