Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Android): Add support for Android concurrent connections (multiple networks) #193

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I

## Table of Contents <!-- omit in toc -->

- [Improvements on this forks](#improvements)
- [Getting started](#getting-started)
- [Overriding `net`](#overriding-net)
- [Overriding `tls`](#overriding-tls)
Expand All @@ -32,6 +33,12 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I
- [Acknowledgments](#acknowledgments)
- [License](#license)

## Impovements on this forks
For apps targeting Android 12 (API level 31) or higher, devices that support concurrent peer-to-peer and internet connections can maintain simultaneous Wi-Fi connections to both the peer device and the primary internet-providing network, making the user experience more seamless.
We add support for Android concurrent connections, routing all data traffic to the peer-to-peer network connection selecting the correct network based on the host DHCP address or subnet address.
This change is essential to open a tcp socket and send data towards ioT devices that expose a WiFi network without internet access.
See: [https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections](https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections).

## Getting started
Install the library using either Yarn:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
Expand All @@ -18,6 +20,9 @@
import com.facebook.react.bridge.ReadableMap;

import java.io.IOException;
import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -76,7 +81,9 @@ public void run() {
// Get the network interface
final String localAddress = options.hasKey("localAddress") ? options.getString("localAddress") : null;
final String iface = options.hasKey("interface") ? options.getString("interface") : null;
selectNetwork(iface, localAddress);
// Get ioT device host to retreive correct network in android concurrent connections
final String iotDeviceHost = options.hasKey("host") ? options.getString("host") : null;
selectNetwork(iface, localAddress, iotDeviceHost);
TcpSocketClient client = new TcpSocketClient(tcpEvtListener, cId, null);
socketMap.put(cId, client);
ReadableMap tlsOptions = pendingTLS.get(cId);
Expand Down Expand Up @@ -213,23 +220,83 @@ public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}

private void requestNetwork(final int transportType) throws InterruptedException {
private void requestNetwork(final int transportType, @Nullable final String iotDeviceHost) throws InterruptedException {
final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
requestBuilder.addTransportType(transportType);
final CountDownLatch awaitingNetwork = new CountDownLatch(1); // only needs to be counted down once to release waiting threads
final ConnectivityManager cm = (ConnectivityManager) mReactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
cm.requestNetwork(requestBuilder.build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
currentNetwork.setNetwork(network);
awaitingNetwork.countDown(); // Stop waiting
}

@Override
public void onUnavailable() {
// smartmedev - add support for for concurrent-connections:
// Route all data to the ioT device network interface if exist more than one concurrent network
// See: https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections
if (cm != null) {
// Get all connected networks
Network[] allNetworks = cm.getAllNetworks();
List<Network> wifiNetworks = new ArrayList<>();

// Check exist at least one newtwork
if (allNetworks != null && allNetworks.length > 0) {
// Filter for retreive only networks based on selected transport type
for (Network network : allNetworks) {
NetworkCapabilities nc = cm.getNetworkCapabilities(network);
if (nc != null && nc.hasTransport(transportType)) {
wifiNetworks.add(network);
}
}

// Check exist at least one newtwork based on selected transport type
if (!wifiNetworks.isEmpty()) {
boolean networkFound = false;
for (Network network : wifiNetworks) {
LinkProperties linkProperties = cm.getLinkProperties(network);
// Ensure linkProperties is not null
if (linkProperties == null)
continue;

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
Inet4Address foundServerAddress = linkProperties.getDhcpServerAddress();
if(iotDeviceHost.equals(foundServerAddress.getHostAddress())) {
smartmedev marked this conversation as resolved.
Show resolved Hide resolved
// found ioT device network
currentNetwork.setNetwork(network);
cm.bindProcessToNetwork(network);
networkFound = true;
awaitingNetwork.countDown(); // Stop waiting
break;
}
} else {
List<LinkAddress> linkAddressList = linkProperties.getLinkAddresses();
if(linkAddressList != null && !linkAddressList.isEmpty()) {
for (LinkAddress address : linkAddressList) {
int lastDotIndex = iotDeviceHost.lastIndexOf('.');
String iotSubnetAddress = iotDeviceHost;
if(lastDotIndex>=0)
iotSubnetAddress = iotDeviceHost.substring(0, lastDotIndex);
if(address.getAddress().getHostAddress().startsWith(iotSubnetAddress)) {
// found ioT device network
currentNetwork.setNetwork(network);
cm.bindProcessToNetwork(network);
networkFound = true;
awaitingNetwork.countDown(); // Stop waiting
break;
}
}
}
}
}
if (!networkFound) {
awaitingNetwork.countDown(); // Stop waiting if no network was found
}
} else {
awaitingNetwork.countDown(); // Stop waiting
}
} else {
awaitingNetwork.countDown(); // Stop waiting
}
});
} else {
awaitingNetwork.countDown(); // Stop waiting
}
// smartmedev - end

// Timeout if there the network is unreachable
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
exec.schedule(new Runnable() {
Expand All @@ -248,7 +315,7 @@ public void run() {
* "cellular" -> Cellular
* etc...
*/
private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress) throws InterruptedException, IOException {
private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress, @Nullable final String iotDeviceHost) throws InterruptedException, IOException {
currentNetwork.setNetwork(null);
if (iface == null) return;
if (ipAddress != null) {
Expand All @@ -260,13 +327,13 @@ private void selectNetwork(@Nullable final String iface, @Nullable final String
}
switch (iface) {
case "wifi":
requestNetwork(NetworkCapabilities.TRANSPORT_WIFI);
requestNetwork(NetworkCapabilities.TRANSPORT_WIFI, iotDeviceHost);
break;
case "cellular":
requestNetwork(NetworkCapabilities.TRANSPORT_CELLULAR);
requestNetwork(NetworkCapabilities.TRANSPORT_CELLULAR, iotDeviceHost);
break;
case "ethernet":
requestNetwork(NetworkCapabilities.TRANSPORT_ETHERNET);
requestNetwork(NetworkCapabilities.TRANSPORT_ETHERNET, iotDeviceHost);
break;
}
if (currentNetwork.getNetwork() == null) {
Expand Down