summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Grover <joeygrover@gmail.com>2018-10-12 10:26:09 -0400
committerGitHub <noreply@github.com>2018-10-12 10:26:09 -0400
commitf1c3b9460584253c1326e7c0b3a0e6f5575e809e (patch)
tree5d48474f63331e895ef1a0fef7472d77ef384574
parentf099f7c6431dd661bd314396722154c57d1499c0 (diff)
parentd23012b933c514edd36b7db068b7934ecdabed77 (diff)
downloadsdl_android-f1c3b9460584253c1326e7c0b3a0e6f5575e809e.tar.gz
Merge pull request #900 from XevoInc/fix/tcp_transport_wifi_binding_3
Fix: make sure `MultiplexTcpTransport` creates a TCP socket over Wi-Fi network
-rw-r--r--sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java354
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTcpTransport.java8
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java2
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/utl/WiFiSocketFactory.java96
4 files changed, 457 insertions, 3 deletions
diff --git a/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java b/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java
new file mode 100644
index 000000000..ab2b7b52d
--- /dev/null
+++ b/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java
@@ -0,0 +1,354 @@
+package com.smartdevicelink.test.transport;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+import android.util.Log;
+
+import com.smartdevicelink.transport.utl.WiFiSocketFactory;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.SocketFactory;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * This is a unit test class for the WiFiSocketFactory class:
+ * {@link com.smartdevicelink.transport.utl.WiFiSocketFactory}
+ *
+ * Requires LOLLIPOP or later since the tests use android.net.NetworkCapabilities class
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class WiFiSocketFactoryTest extends TestCase {
+
+ private static final String TAG = WiFiSocketFactoryTest.class.getSimpleName();
+
+ private Context mMockContext;
+ private PackageManager mMockPackageManager;
+ private ConnectivityManager mMockConnMan;
+ private SocketFactory mMockSocketFactory; // this is the SocketFactory that creates mWiFiBoundSocket
+ private Socket mWiFiBoundSocket;
+
+ private enum FactoryRet {
+ RETURNS_NULL,
+ RETURNS_CORRECT_FACTORY,
+ RETURNS_ANOTHER_FACTORY,
+ }
+
+ private class MockNetworkConfig {
+ // true to make a null Network
+ boolean isNull;
+ // specify the transport type of the Network
+ int transportType;
+ // spcify the type of SocketFactory returned from this Network
+ FactoryRet factoryReturnValue;
+
+ MockNetworkConfig(boolean isNull, int transportType, FactoryRet factoryReturnValue) {
+ this.isNull = isNull;
+ this.transportType = transportType;
+ this.factoryReturnValue = factoryReturnValue;
+ }
+ }
+
+ private void setupMockNetworks(MockNetworkConfig[] configs) {
+ if (configs == null) {
+ when(mMockConnMan.getAllNetworks()).thenReturn(null);
+ return;
+ }
+
+ List<Network> networkList = new ArrayList<Network>(configs.length);
+
+ for (MockNetworkConfig config : configs) {
+ if (config.isNull) {
+ networkList.add(null);
+ continue;
+ }
+
+ Network network = mock(Network.class);
+
+ NetworkCapabilities networkCapabilities = createNetworkCapabilitiesWithTransport(config.transportType);
+ when(mMockConnMan.getNetworkCapabilities(network)).thenReturn(networkCapabilities);
+
+ SocketFactory factory = null;
+ switch (config.factoryReturnValue) {
+ case RETURNS_NULL:
+ break;
+ case RETURNS_CORRECT_FACTORY:
+ factory = mMockSocketFactory;
+ break;
+ case RETURNS_ANOTHER_FACTORY:
+ // create another mock SocketFactory instance
+ factory = mock(SocketFactory.class);
+ break;
+ }
+ when(network.getSocketFactory()).thenReturn(factory);
+
+ networkList.add(network);
+ }
+
+ when(mMockConnMan.getAllNetworks()).thenReturn(networkList.toArray(new Network[networkList.size()]));
+ }
+
+ private static NetworkCapabilities createNetworkCapabilitiesWithTransport(int transport) {
+ // Creates a dummy NetworkCapabilities instance.
+ // Since NetworkCapabilities class is 'final', we cannot create its mock. To create a dummy
+ // instance, here we use reflection to call its constructor and a method that are marked
+ // with "@hide".
+ // It is possible that these methods will not be available in a future version of Android.
+ // In that case we need to update our code accordingly.
+ Class<NetworkCapabilities> c = NetworkCapabilities.class;
+ try {
+ Method addTransportTypeMethod = c.getMethod("addTransportType", int.class);
+ addTransportTypeMethod.setAccessible(true);
+
+ NetworkCapabilities instance = c.getDeclaredConstructor().newInstance();
+ addTransportTypeMethod.invoke(instance, transport);
+ Log.e(TAG, "Yes successful");
+ return instance;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to create NetworkCapabilities instance using reflection: ", e);
+ return null;
+ }
+ }
+
+ // from https://stackoverflow.com/questions/40300469/mock-build-version-with-mockito
+ // and https://stackoverflow.com/questions/13755117/android-changing-private-static-final-field-using-java-reflection
+ private static void setFinalStatic(Field field, Object newValue) throws Exception {
+ field.setAccessible(true);
+// Field modifiersField = Field.class.getDeclaredField("modifiers");
+ // This call might fail on some devices (for example, Nexus 6 with Android 5.0.1).
+ // If that's the issue, we might want to introduce PowerMock.
+ Field modifiersField = Field.class.getDeclaredField("accessFlags");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ field.set(null, newValue);
+ }
+
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mMockContext = mock(Context.class);
+ mMockPackageManager = mock(PackageManager.class);
+ mMockConnMan = mock(ConnectivityManager.class);
+
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getPackageName()).thenReturn("dummyPackageName");
+ when(mMockContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mMockConnMan);
+
+ when(mMockPackageManager.checkPermission(eq(Manifest.permission.ACCESS_NETWORK_STATE), anyString())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ mMockSocketFactory = mock(SocketFactory.class);
+ mWiFiBoundSocket = new Socket();
+ when(mMockSocketFactory.createSocket()).thenReturn(mWiFiBoundSocket);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ // test the happy path
+ public void testWithWiFiNetwork() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret);
+ }
+
+ // test the case where SDK_INT is less than 21
+ /* This is disabled since Travis CI uses an AVD with 5.1.1 and setFinalStatic() doesn't work on it.
+ public void testPriorToLollipop() throws Exception {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ // simulate SDK_INT to less than LOLLIPOP
+ int previousSDKInt = Build.VERSION.SDK_INT;
+ setFinalStatic(Build.VERSION.class.getField("SDK_INT"), Build.VERSION_CODES.KITKAT_WATCH);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ // make sure we revert our change
+ setFinalStatic(Build.VERSION.class.getField("SDK_INT"), previousSDKInt);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since it is not available prior to LOLLIPOP",
+ mWiFiBoundSocket, ret);
+ }
+ */
+
+ // test the case where we do not have ACCESS_NETWORK_STATE permission
+ public void testWithoutPermission() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ // simulate the case where required permission isn't available
+ when(mMockPackageManager.checkPermission(eq(Manifest.permission.ACCESS_NETWORK_STATE), anyString())).thenReturn(PackageManager.PERMISSION_DENIED);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since we don't have required permission",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where context.getPackageManager() returns null
+ public void testPackageManagerNull() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ // simulate the case where ConnectivityManager isn't available
+ when(mMockContext.getPackageManager()).thenReturn(null);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since PackageManager isn't available",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where getSystemService() returns null
+ public void testConnectivityManagerNull() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ // simulate the case where ConnectivityManager isn't available
+ when(mMockContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(null);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since ConnectivityManager isn't working",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where ConnectivityManager returns null for the network list
+ public void testNetworkListNull() {
+ setupMockNetworks(null);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since Network list isn't available",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where the network list contains a null for Network instance
+ public void testNetworkListHasNull() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ // multiple Network instances in the list, the first one being NULL
+ new MockNetworkConfig(true, 0, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret);
+ }
+
+ // test the case where the phone isn't connected to Wi-Fi network
+ public void testNoWiFiNetwork() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ // none of the instances has TRANSPORT_WIFI in their capabilities
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_BLUETOOTH, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_VPN, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ });
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since Wi-Fi network isn't available",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where we get null for SocketFactory
+ public void testSocketFactoryNull() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_NULL),
+ });
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since SocketFactory isn't available",
+ mWiFiBoundSocket, ret);
+ }
+
+ // test the case where we get a null for SocketFactory, then a valid one for another
+ public void testSocketFactoryNull2() {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_NULL),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret);
+ }
+
+ // test the case where we get an exception with SocketFactory.createSocket()
+ public void testFactoryReturnsException() throws IOException {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ when(mMockSocketFactory.createSocket()).thenThrow(new IOException("Dummy IOException for testing!"));
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertNotSame("Returned Socket shouldn't be created through SocketFactory since it throws an IOException",
+ mWiFiBoundSocket, ret);
+ }
+
+ // Test the case we get multiple Network instances with Wi-Fi transport, and the SocketFactory of
+ // the first one throws Exception and the other one succeeds.
+ // This is to simulate Samsung Galaxy S9.
+ public void testFactoryReturnsException2() throws IOException {
+ setupMockNetworks(new MockNetworkConfig[] {
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY),
+ });
+
+ when(mMockSocketFactory.createSocket()).thenThrow(new IOException("Dummy IOException for testing!"))
+ .thenReturn(mWiFiBoundSocket);
+
+ Socket ret = WiFiSocketFactory.createSocket(mMockContext);
+
+ assertNotNull("createSocket() should always return a Socket instance", ret);
+ assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret);
+ }
+}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTcpTransport.java b/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTcpTransport.java
index 71dd8f6ee..d5843f4c2 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTcpTransport.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTcpTransport.java
@@ -32,6 +32,7 @@
package com.smartdevicelink.transport;
+import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -39,6 +40,7 @@ import android.util.Log;
import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.WiFiSocketFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -65,14 +67,16 @@ public class MultiplexTcpTransport extends MultiplexBaseTransport {
private OutputStream mOutputStream = null;
private MultiplexTcpTransport.TcpTransportThread mThread = null;
private WriterThread writerThread;
+ private Context mContext;
- public MultiplexTcpTransport(int port, String ipAddress, boolean autoReconnect, Handler handler) {
+ public MultiplexTcpTransport(int port, String ipAddress, boolean autoReconnect, Handler handler, Context context) {
super(handler, TransportType.TCP);
this.ipAddress = ipAddress;
this.port = port;
connectedDeviceAddress = ipAddress + ":" + port;
this.autoReconnect = autoReconnect;
+ mContext = context;
setState(STATE_NONE);
}
@@ -214,7 +218,7 @@ public class MultiplexTcpTransport extends MultiplexBaseTransport {
}
logInfo(String.format("TCPTransport.connect: Socket is closed. Trying to connect to %s", getAddress()));
- mSocket = new Socket();
+ mSocket = WiFiSocketFactory.createSocket(mContext);
mSocket.connect(new InetSocketAddress(ipAddress, port));
mOutputStream = mSocket.getOutputStream();
mInputStream = mSocket.getInputStream();
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
index ec61a13e3..50c5b5412 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
@@ -650,7 +650,7 @@ public class SdlRouterService extends Service{
}//else { TCP transport does not exists.}
//TCP transport either doesn't exist or is not connected. Start one up.
- service.tcpTransport = new MultiplexTcpTransport(port, ipAddress, true, service.tcpHandler);
+ service.tcpTransport = new MultiplexTcpTransport(port, ipAddress, true, service.tcpHandler, service);
service.tcpTransport.start();
}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/utl/WiFiSocketFactory.java b/sdl_android/src/main/java/com/smartdevicelink/transport/utl/WiFiSocketFactory.java
new file mode 100644
index 000000000..b35070d89
--- /dev/null
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/utl/WiFiSocketFactory.java
@@ -0,0 +1,96 @@
+package com.smartdevicelink.transport.utl;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import javax.net.SocketFactory;
+
+import static com.smartdevicelink.util.NativeLogTool.logInfo;
+
+public class WiFiSocketFactory {
+ /**
+ * Try to create a TCP socket which is bound to Wi-Fi network (for Android 5+)
+ *
+ * On Android 5 and later, this method tries to create a Socket instance which is bound to a
+ * Wi-Fi network. If the phone is not connected to a Wi-Fi network, or the app lacks
+ * required permission (ACCESS_NETWORK_STATE), then this method simply creates a Socket instance
+ * with "new Socket();".
+ *
+ * @return a Socket instance, preferably bound to a Wi-Fi network
+ */
+ public static Socket createSocket(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Socket socket = createWiFiSocket(context);
+ if (socket != null) {
+ logInfo("Created a Socket bound to Wi-Fi network");
+ return socket;
+ }
+ }
+
+ return new Socket();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static Socket createWiFiSocket(Context context) {
+ PackageManager pm = context.getPackageManager();
+ if (pm == null) {
+ logInfo("PackageManager isn't available.");
+ return null;
+ }
+ // getAllNetworks() and getNetworkCapabilities() require ACCESS_NETWORK_STATE
+ if (pm.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, context.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ logInfo("Router service doesn't have ACCESS_NETWORK_STATE permission. It cannot bind a TCP transport to Wi-Fi network.");
+ return null;
+ }
+
+ ConnectivityManager connMan = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connMan == null) {
+ logInfo("ConnectivityManager isn't available.");
+ return null;
+ }
+
+ Network[] allNetworks = connMan.getAllNetworks();
+ if (allNetworks == null) {
+ logInfo("Failed to acquire a list of networks.");
+ return null;
+ }
+
+ // Samsung Galaxy S9 (with Android 8.0.0) provides two `Network` instances which have
+ // TRANSPORT_WIFI capability. The first one throws an IOException upon creating a Socket,
+ // and the second one actually works. To support such case, here we iterate over all
+ // `Network` instances until we can create a Socket.
+ for (Network network : allNetworks) {
+ if (network == null) {
+ continue;
+ }
+
+ NetworkCapabilities capabilities = connMan.getNetworkCapabilities(network);
+ if (capabilities == null) {
+ continue;
+ }
+
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ try {
+ SocketFactory factory = network.getSocketFactory();
+ if (factory != null) {
+ return factory.createSocket();
+ }
+ } catch (IOException e) {
+ logInfo("IOException during socket creation (ignored): " + e.getMessage());
+ }
+ }
+ }
+
+ logInfo("Cannot find Wi-Fi network to bind a TCP transport.");
+ return null;
+ }
+}