19
votes

I have to send UDP packets to a WiFi module (provided with own AP) with no internet connection but when I connect the mobile with the AP, Android redirects my packets on the mobile data interface because it has got internet connection.

I've used the code below to do my job but it seems not working on Android M.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setWifiInterfaceAsDefault() {
    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

    NetworkRequest.Builder builder = new NetworkRequest.Builder();
    NetworkRequest networkRequest= builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .build();

    connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
}

I've also added

<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />

on my AndroidManifest.xml and I ensured myself that Settings.System.canWrite(this) returns true but still nothing.

Thanks in advance.

2
My friend did you figure out a solution? I have the exact requirement.AppleGrew
I have done a workaround simulating a connection on the IoT device to let Android think that the IoT device had a connection. You can do that placing a void file called generate_204 on the server root and adding some urls to your server dns. I know it's a weird workaround but for me worked. Hope I've helped you.Alessandro Muzzi
Thanks, but unfortunately that is not an option for me. IoT device is not in my control.AppleGrew
If you'll find a real solution, please let us know ✌Alessandro Muzzi
@AleMuzzi I have a similar question would you mind giving more information on your generate_204 workaround as i can make changes to the device stackoverflow.com/q/42492136/1685748Lonergan6275

2 Answers

20
votes

Stanislav's answer is correct but incomplete because only works in Lollipop.

I've wrote a complete solution for Lollipop and Marshmallow onwards for you to route all network requests through WiFi when connected to a specific network of your choice.


Kotlin

In your Activity,

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class RoutingActivity : Activity() {

    private var mConnectivityManager: ConnectivityManager? = null
    private var mNetworkCallback: ConnectivityManager.NetworkCallback? = null
    //...

    override fun onCreate(savedInstanceState: Bundle?) {
        //...
        routeNetworkRequestsThroughWifi("Access-Point-SSID-You-Want-To-Route-Your-Requests")
    }

Route future network requests from application through WiFi (even if given WiFi network is without internet and mobile data has internet connection)

/**
 * This method sets a network callback that is listening for network changes and once is
 * connected to the desired WiFi network with the given SSID it will bind to that network.
 *
 * Note: requires android.permission.INTERNET and android.permission.CHANGE_NETWORK_STATE in
 * the manifest.
 *
 * @param ssid The name of the WiFi network you want to route your requests
 */
private fun routeNetworkRequestsThroughWifi(ssid: String) {
    mConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    // ensure prior network callback is invalidated
    unregisterNetworkCallback(mNetworkCallback)

    // new NetworkRequest with WiFi transport type
    val request = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .build()

    // network callback to listen for network changes
    mNetworkCallback = object : ConnectivityManager.NetworkCallback() {

        // on new network ready to use
        override fun onAvailable(network: Network) {

            if (getNetworkSsid(this@RoutingActivity).equals(ssid, ignoreCase = false)) {
                releaseNetworkRoute()
                createNetworkRoute(network)

            } else {
                releaseNetworkRoute()
            }
        }
    }
    mConnectivityManager?.requestNetwork(request, mNetworkCallback)
}

Unregister network callback

private fun unregisterNetworkCallback(networkCallback: ConnectivityManager.NetworkCallback?) {
    if (networkCallback != null) {
        try {
            mConnectivityManager?.unregisterNetworkCallback(networkCallback)

        } catch (ignore: Exception) {
        } finally {
            mNetworkCallback = null
        }
    }
}

Create network route

private fun createNetworkRoute(network: Network): Boolean? {
    var processBoundToNetwork: Boolean? = false
    when {
    // 23 = Marshmallow
        Build.VERSION.SDK_INT >= 23 -> {
            processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(network)
        }

    // 21..22 = Lollipop
        Build.VERSION.SDK_INT in 21..22 -> {
            processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(network)
        }
    }
    return processBoundToNetwork
}

 Release network route

private fun releaseNetworkRoute(): Boolean? {
    var processBoundToNetwork: Boolean? = false
    when {
    // 23 = Marshmallow
        Build.VERSION.SDK_INT >= 23 -> {
            processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(null)
        }

    // 21..22 = Lollipop
        Build.VERSION.SDK_INT in 21..22 -> {
            processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(null)
        }
    }
    return processBoundToNetwork
}

Helper

private fun getNetworkSsid(context: Context?): String {
    // WiFiManager must use application context (not activity context) otherwise a memory leak can occur
    val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
    if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
        return wifiInfo.ssid.removeSurrounding("\"")
    }
    return ""
}
12
votes

Bind the network using ConnectivityManager.setProcessDefaultNetwork() prevents roaming and allows for full TCP access. Thus, within the onAvailable() callback you could bind the application process to that network rather than opening a connection to a particular URL.

ConnectivityManager connection_manager = 
(ConnectivityManager) activity.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkRequest.Builder request = new NetworkRequest.Builder();
request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);

connection_manager.registerNetworkCallback(request.build(), new NetworkCallback() {

    @Override
    public void onAvailable(Network network) {
        ConnectivityManager.setProcessDefaultNetwork(network);
    }
}

Original answer