22

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.

Alessandro Muzzi
  • 808
  • 1
  • 12
  • 26
  • My friend did you figure out a solution? I have the exact requirement. – AppleGrew Sep 14 '16 at 18:50
  • 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 Sep 19 '16 at 09:44
  • Thanks, but unfortunately that is not an option for me. IoT device is not in my control. – AppleGrew Sep 19 '16 at 11:39
  • If you'll find a real solution, please let us know ✌ – Alessandro Muzzi Sep 20 '16 at 21:42
  • @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 http://stackoverflow.com/q/42492136/1685748 – Lonergan6275 Mar 06 '17 at 10:28
  • @Lonergan6275 Different OS (it also depends on OS version) checks internet connections in different ways; Android asks some google servers a void file called "generate_204" to ensure that the current net has internet connection. If you can provide that file to your device, it will show you that there is an internet connection. Checked urls are: - "connectivitycheck.gstatic.com" - "clients3.google.com" Anyway, you can see what effectively your device does tracking its packets with "tPacketCapture" app and WireShark – Alessandro Muzzi Mar 07 '17 at 17:19
  • @AleMuzzi so how do i point `connectivitycheck.gstatic.com` - `clients3.google.com` to the web root of my iot device where i can place the `generate_204` file – Lonergan6275 Mar 08 '17 at 11:13
  • @Lonergan6275 generate_204 has to be put in the root of your http server in the IoT device and you have to have a dos server on the device that provides those urls, for example if you are using openwrt as IoT OS you will have a file named "hosts" under /etc, add the following lines: 10.1.1.1 connectivitycheck.gstatic.com 10.1.1.1 clients3.google.com. Where 10.1.1.1 is your "127.0.0.1" IoT address – Alessandro Muzzi Mar 08 '17 at 17:17
  • I know it's really ugly written like this, sorry – Alessandro Muzzi Mar 08 '17 at 17:20
  • @AleMuzzi Thank you very much I dont know a lot on the IOT Device side but will editing the hosts be sufficient as we would have push it in an update to many devices. or is the DNS Server a requirement? – Lonergan6275 Mar 09 '17 at 10:53
  • @Lonergan6275 Sorry, I think I've not understood your question but if I have understood, I can tell you that OpenWRT provides itself a DNS server, that's why you have an hosts file containing the well known hosts (with static IP) so, hoping that google will not change the internet check system, adding those addresses will be enough – Alessandro Muzzi Mar 09 '17 at 13:32

2 Answers2

21

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 ""
}
Ryan Amaral
  • 4,059
  • 1
  • 41
  • 39
  • 1
    More information about this can be found here: https://android-developers.googleblog.com/2016/07/connecting-your-app-to-wi-fi-device.html – Ryan Amaral Jul 22 '18 at 22:45
  • 3
    You should consider putting up your own blog post about this; this remains an under-documented topic with a steady stream of SO questions, and your writeup here and at https://stackoverflow.com/a/51470122/78356 provide a much more complete and useful picture than the googleblog post – mph Mar 25 '19 at 01:29
  • 1
    Much appreciated for your positive feedback @mikeh! Feel free to write an article yourself with this content and I kindly volunteer to review it before you publish it. – Ryan Amaral Mar 26 '19 at 20:07
  • 1
    Thanks @ryan-amaral, that is indeed very kind. Let me see what I can do! – mph Mar 27 '19 at 06:06
  • mConnectivityManager?.bindProcessToNetwork(null) - this line did it for me. – Mikkel Larsen Jun 24 '19 at 09:13
  • Important - it seems the `getNetworkSsid()` function MUST instantiate a fresh `WifiManager` as shown (i.e. not use an existing copy declared at the top of a class, etc) or it will likely return the WRONG SSID. Maybe something to do with the `connectionInfo` or `supplicantState` getters? – rmirabelle Feb 22 '20 at 19:27
  • 1
    I am connecting a device without internet. In some devices this code is working but in some devices android automatic disconnects wifi if it is not having internet. In same mobile, if we connect wifi manually instead of programatically then it is asking to keep it or not. Is there any solution to remain connected with the wifi which is not having internet ? – Khushbu Shah Mar 03 '20 at 06:22
  • @RyanAmaral Thanks its really nice post can you please help me which method we have to call what like onResume and onStar method what we have to call i am getting alway unknow ssid so if (getNetworkSsid(this@Rove3MainActivity).equals(ssid, ignoreCase = false)) { this condition fail – MARSH Feb 15 '23 at 14:52
  • Thanks, @MARSH. As from Android 8 is necessary to request location permission for us to be able to retrieve the SSID. That might be the reason why you're getting . – Ryan Amaral Feb 16 '23 at 15:42
  • @KhushbuShah is required to connect to the wifi network programmatically, otherwise will not work as we don't have permission to change the route of a wifi network set up by the user, i.e. manually connected. – Ryan Amaral Feb 16 '23 at 15:47
13

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

Community
  • 1
  • 1