12

Working on an android application in which I need to connect WiFi device programatically which does not have internet. Here is a code:

    private void connectToWiFi(final String ssid, String password) {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {

            WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
            final ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

            NetworkRequest.Builder request = new NetworkRequest.Builder();
            request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
            request.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); // Internet not required

            ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {

                @Override
                public void onAvailable(Network network) {

                    String networkSSID = getNetworkSsid();

                    if (networkSSID.equals(ssid)) {
                        connectivityManager.bindProcessToNetwork(network);
                    }
                }

                @Override
                public void onUnavailable() {
                    super.onUnavailable();
                }

                @Override
                public void onLost(@NonNull Network network) {
                    super.onLost(network);
                }
            };
            connectivityManager.registerNetworkCallback(request.build(), networkCallback);

            WifiConfiguration config = new WifiConfiguration();
            config.SSID = String.format("\"%s\"", ssid);

            int netId = -1;
            List<WifiConfiguration> apList = wifiManager.getConfiguredNetworks();

            for (WifiConfiguration i : apList) {

                if (i.SSID != null && i.SSID.equals("\"" + ssid + "\"")) {
                    netId = i.networkId;
                }
            }

            // Add network in Saves network list if it is not available in list
            if (netId == -1) {

                if (TextUtils.isEmpty(password)) {
                    Log.d(TAG, "====== Connect to open network");
                    config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                } else {
                    Log.d(TAG, "====== Connect to secure network");
                    config.preSharedKey = String.format("\"%s\"", password);
                }

                netId = wifiManager.addNetwork(config);
            }

            Log.d(TAG, "Connect to Network : " + netId);
            wifiManager.enableNetwork(netId, true);

        } else {

            // For Android 10 and above
            // WifiNetworkSpecifier code
        }
    }

Permissions used :

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Above code works fine for the networks that offer internet connection. But it fails in most of the cases for the networks without internet connection.

When attempting to connect to a WiFi configuration which does not have internet access, we need to bind our app's process to that network, or configure our HTTP client to use that network's socket factory, otherwise the system will disconnect from this WiFi network.

As per this guideline , bindProcessToNetwork is already applied. But system is disconnecting from the network regardless of the use of bindToProcess() / routing traffic over the socket factory. It is not allowing to remain connected with the WiFi which don't have internet. It is really surprising, Android has never thought for IoT device use cases before restricting to network connection.

When attempting to connect to a WiFi network added by my app or system settings app or any other app, the system logs reveal:

UID xxxxx does not have permission to update configuration

Same error is not occurring when app tries to connect device which has internet even if that network is added by some other app.

So the app is allowed to connect to the previous configuration, albeit with 'insufficient permissions', and traffic is momentarily routed over this network. But after a few seconds, the network is disconnected, and the system attempts to re-associate with some other internet-enabled network.

Testing on Moto G5 Plus, Android 8.1.0, but I believe this is a platform bug, not device specific. And mostly this bug is introduced from Android 7 or something because it was working previously.

I have also reported issue here. Also provided sample app in this issue.

Is there any solution available for this issue ? Is there any option of paid support from Android ?

Thanks in advance.

Khushbu Shah
  • 1,603
  • 3
  • 27
  • 45
  • Although, [official docs](https://developer.android.com/reference/android/net/wifi/WifiConfiguration.Status) are a bit messed up, this can work -> `conf.status = WifiConfiguration.Status.ENABLED;`. Try setting this. Not sure about it, this is why as a comment. – Lalit Fauzdar Jun 11 '20 at 10:00
  • Tried. Didn't work. – Khushbu Shah Jun 11 '20 at 10:22
  • Here is the doc on how Wi-Fi networks are evaluated: https://source.android.com/devices/tech/connect/wifi-network-selection#network-evaluators You may want to look into using the Wi-Fi suggestion API. – Always Learning Jun 19 '20 at 00:02
  • Wi-Fi suggestion and Wi-Fi Network Specifier APIs are for Android 10. Implemented Wi-Fi Network Specifier API for Android 10 but not able to find any solution for below Android 10 – Khushbu Shah Jun 19 '20 at 07:25
  • Can you try to run your app with mobile data diasbled in the Settings? Tell me the result, please. – Alex Rmcf Jun 19 '20 at 08:23
  • Got same result. Tested on both with and without sim card phones. – Khushbu Shah Jun 19 '20 at 09:18
  • Also checked [this](https://stackoverflow.com/questions/48749798/after-connecting-to-wifi-programmatically-disconnects-a-few-seconds-later/50062119), [this](https://stackoverflow.com/questions/53956062/how-to-update-an-already-created-wi-fi-configuration-or-uid-xxx-does-not-have) etc links. – Khushbu Shah Jun 19 '20 at 09:20
  • Do these answer your question? [Answer 1](https://android.stackexchange.com/questions/131132/force-marshmallow-to-keep-a-wi-fi-without-internet-access) & [answer 2](https://android.stackexchange.com/questions/130265/stay-connected-to-specific-wifi-which-has-no-internet) – Qumber Jun 24 '20 at 05:09
  • [Answer 1](https://android.stackexchange.com/questions/131132/force-marshmallow-to-keep-a-wi-fi-without-internet-access) cannot be done programatically. For [Answer 2](https://android.stackexchange.com/questions/130265/stay-connected-to-specific-wifi-which-has-no-internet) , cannot do DHCP settings in app currently. – Khushbu Shah Jun 24 '20 at 10:20
  • Did you resolve it? Having the same issue in a Xiaomi Rednote 10 with Android 11; When I apply the bind to the network connected, it disconnects 3-5 seconds later – Rudy_TM Jul 22 '21 at 16:30

3 Answers3

0

The problem you mentioned might not be because of the code you provided at Issuetracker. Once i fixed the problem in my device (Android 9), i am unable to replicate the error even after a complete undo. I even tried device reboot, app reinstallation or even factory reset. Try this.

  1. In Setting --> Apps & Permissions, the test app requests for a Location permission. Give it the permisson.

  2. Java code can be

    private void connectToWiFi(final String ssid, String password) {
    
    if (connectivityManager != null) {
    
        try {
            connectivityManager.bindProcessToNetwork(null);
            connectivityManager.unregisterNetworkCallback(networkCallback);
        } catch (Exception e) {
            Log.d(TAG, "Connectivity Manager is already unregistered");
        }
    }
    
    NetworkRequest.Builder request = new NetworkRequest.Builder();
    request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
    if(isOnline()==false){
        request.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    }
    
    networkCallback = new ConnectivityManager.NetworkCallback() {
    
        @Override
        public void onAvailable(Network network) {
    
            String networkSSID = getNetworkSsid();
            Log.e(TAG, "Network is available - " + networkSSID);
    
            if (networkSSID.equals(ssid)) {
                if(isOnline()==false){
                    Log.e(TAG, "bindProcessToNetwork");
                    connectivityManager.bindProcessToNetwork(network);
                }
            }
        }
    
        @Override
        public void onUnavailable() {
            super.onUnavailable();
            Log.e(TAG, "Network is Unavailable");
        }
    
        @Override
        public void onLost(@NonNull Network network) {
            super.onLost(network);
            Log.e(TAG, "Lost Network Connection");
        }
    };
    
    connectivityManager.registerNetworkCallback(request.build(), networkCallback);
    
    if (!wifiManager.isWifiEnabled()) {
        wifiManager.setWifiEnabled(true);
    }
    
    WifiConfiguration config = new WifiConfiguration();
    config.SSID = String.format("\"%s\"", ssid);
    config.status = WifiConfiguration.Status.ENABLED;
    
    int netId = -1;
    List<WifiConfiguration> apList = wifiManager.getConfiguredNetworks();
    Log.d(TAG, "List Size : " + apList.size());
    
    for (WifiConfiguration i : apList) {
    
        if (i.SSID != null && i.SSID.equals("\"" + ssid + "\"")) {
            netId = i.networkId;
        }
    }
    
    // Add network in Saves network list if it is not available in list
    if (netId == -1) {
    
        if (TextUtils.isEmpty(password)) {
            Log.e(TAG, "====== Connect to open network");
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
        } else {
            Log.e(TAG, "====== Connect to secure network");
            config.preSharedKey = String.format("\"%s\"", password);
        }
    
        netId = wifiManager.addNetwork(config);
    }
    
    Log.d(TAG, "Connect to network : " + netId);
    wifiManager.enableNetwork(netId, true);
    
    }
    
    public boolean isOnline() {
    Runtime runtime = Runtime.getRuntime();
    try {
        Process ipProcess = runtime.exec("/system/bin/ping -c 1 8.8.8.8");
        int     exitValue = ipProcess.waitFor();
        return (exitValue == 0);
    }
    catch (IOException e)          { e.printStackTrace(); }
    catch (InterruptedException e) { e.printStackTrace(); }
    
    return false;
    }
    
  • Sorry I forgot to mention that App is already have above listed permission. Checked in Settings -> Apps & permissions. Forgot to add runtime permission check only in sample app. – Khushbu Shah Jun 19 '20 at 09:45
  • hey. can you wrap the line `request.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);` in an (if condition) which runs it only if the network is not capable of internet. I copied that if condition from the accepted answer of this question https://stackoverflow.com/questions/4238921/detect-whether-there-is-an-internet-connection-available-on-android – Nilesh Adhikari Jun 19 '20 at 10:00
  • Yes you can do that. I have special case of without internet only as mention in title. Anyway, I tried with commenting that line too. But same result. – Khushbu Shah Jun 19 '20 at 10:09
  • can you also try commenting `connectivityManager.bindProcessToNetwork(network);` because i solved your problem by wrapping `request.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);` and `connectivityManager.bindProcessToNetwork(network);` in (if condition) – Nilesh Adhikari Jun 19 '20 at 10:20
  • If phone has mobile data on and also connected with the wifi device which don't have internet, then OS automatically send all request to the network which has internet. It means it will use mobile data instead of communicating with wifi device. `bindProcessToNetwork` is needed to route request to connected network. Without that line app cannot communicate with the wifi device which don't have internet. Please read documentation and understand use of `bindProcessToNetwork`. – Khushbu Shah Jun 19 '20 at 10:25
  • What i meant was wrap `connectivityManager.bindProcessToNetwork(network);` in an if condition which runs it only if the network is not capable of internet. You have registered a networkCallBack with `request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);` so i dont think mobile data interference will be a problem. – Nilesh Adhikari Jun 19 '20 at 10:37
0

If you are targeting to use your code in specific networks then you can go to yours access point interface and simply set the DHCP settings to static and give the devices an appropriate IP.

More info can be found here

Now if you are targeting networks that does you cannot access their DHCP settings, based on that link you can tell your device to stay connected to the network no matter if it has internet connection.

NetworkRequest.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    builder = new NetworkRequest.Builder();
    //set the transport type do WIFI
    builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
    connectivityManager.requestNetwork(builder.build(), new
                                 ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (Build.VERSION.RELEASE.equalsIgnoreCase("6.0")) {
                    if (!Settings.System.canWrite(mActivity)) {
                        Intent goToSettings = new  Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
                        goToSettings.setData(Uri.parse("package:" + 
                        mActivity.getPackageName()));
                        mActivity.startActivity(goToSettings);
                    }
                }
                connectivityManager.bindProcessToNetwork(null);
                if (mSsid.contains("my_iot_device-xxxxxxxxx"))
                    connectivityManager.bindProcessToNetwork(network);
                else {
                    //This method was deprecated in API level 23
                    ConnectivityManager.setProcessDefaultNetwork(null);
                    if (mSsid.contains("my_iot_device-xxxxxxxxx"))
                        ConnectivityManager.setProcessDefaultNetwork(network);
                }
                connectivityManager.unregisterNetworkCallback(this);
            }
        }
    });
}

Also sometimes it is good to keep your wifi radio awake, so you gonna need to hold it using WiFiLock.

//Create a wifi manager instance first
WifiManager mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
//and then create your wifiLock
WifiManager.WifiLock mWifiLock = mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "wifiLock");
if(!mWifiLock.isHeld())
        mWifiLock.acquire();

Later if you want to realese it

if(mWifiLock.isHeld())
        mWifiLock.release();

This code helped me to stay connected to my network even if it not has internet access.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
JexSrs
  • 155
  • 6
  • 18
  • cannot do DHCP settings. I tried with above WifiLock code. But it didn't work. Still disconnecting from network. This issue is not occurring every time. Sometimes device stay connected, but once it is reproduced then it is faced by every time even after restarting app. – Khushbu Shah Jun 24 '20 at 10:13
  • Well, I am sure this is not a solution, but if the phone is rooted then you can use this solution https://android.stackexchange.com/a/148442/271172. Anyway I will keep searching, to find something that will not require a rooted device – JexSrs Jun 24 '20 at 12:50
  • Also this way is a little dirty but, you can capture the wifi events and if the device is disconnected from network, try to reconnect,,, – JexSrs Jun 24 '20 at 12:58
  • Good idea of reconnecting but my app needs to do some session creation and communication sequentially with device after connecting it. Once this issue is reproduced then it will disconnect every time after few second so cannot execute same data transfer again and again because before completing whole flow device will be disconnected. Really thanks for your efforts. Let me know if you found any solution for non-rooted phones or any alternative workaround. – Khushbu Shah Jun 24 '20 at 13:08
  • If the session creation and communication is created in steps then you can continue from the exact point it stopped when your device disocnnected – JexSrs Jun 24 '20 at 13:17
0

Replace:

  • connectivityManager.registerNetworkCallback link

With

  • connectivityManager.requestNetwork link

Relevant docs on requestNetwork from the link above:

As long as this request is outstanding, the platform will try to maintain the best network matching this request, while always attempting to match the request to a better network if possible.

Note the above docs are not included for registerNetworkCallback.

The Android Connectivity stack will tear down unused networks. This is tracked by the number of requests a network has by requestNetwork. Requests filed as part of registerNetworkCallback are NOT counted therefore as far as the Connectivity stack is concerned, the Wi-Fi network you are requesting is not being requested and can therefore be torn down.

This doesn't apply for the default network, however only networks with internet capability are eligible to be the default which does not apply in your case.

Always Learning
  • 2,623
  • 3
  • 20
  • 39