Search code examples
androidnetwork-programmingandroid-wifiandroid-6.0-marshmallowiot

Send request over WiFi (without connection) even if Mobile data is ON (with connection) on Android M


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.


Solution

  • 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 ""
    }