Search code examples
androidraspberry-piwifiiot

How do I keep Android connected to an IoT WiFi AP with no internet access when an internet-enabled WiFi network is available?


I'm developing a Raspberry Pi-based IoT device which hosts its own WiFi AP (no internet) and have successfully followed this Android Developers blog post from 2016 to connect to it and route traffic to it from my Android app, even when mobile data is enabled (i.e. via Network#getSocketFactory). (i.e. I'm NOT having the issue reported in this question: Send request over WiFi (without connection) even if Mobile data is ON (with connection) on Android M)

The problem now is that my Android 10 device (a Google Pixel) automatically disconnects from the network and switches to my home WiFi network (with internet) after a few minutes. This is with the app in the foreground, with an active web socket connection to a server-side app running on the Pi.

I could work around the problem by listening for network state changes in the app and forcibly reconnecting to my IoT network via WifiManager#enableNetwork, but this seems like a hacky solution and the connection would still be interrupted, leading to a poor user experience.

Another thought I had was to use WifiManager#disableNetwork to disable all other configured WiFi networks, thus preventing the phone from connecting to them. However the doc states that disabling networks created by other apps is not allowed.

Maintaining a connection to an IoT WiFi network with no net access seems to be a reasonable use case for an Android app that Google are (or were) aware of, but I'm struggling to piece together the current best practices for how to do this in 2020. I'm wondering if it's achievable via the newer WiFi suggestion APIs. However these sound like are even more restrictive to app developers and don't offer any guarantees about the WiFi network that will actually be joined.


Solution

  • Got to the bottom of it. The solution that worked for me for Android 10 was this one: https://stackoverflow.com/a/58770341/1405990

    I.e. using WifiNetworkSpecifier and ConnectivityManager#requestNetwork:

            val builder: NetworkRequest.Builder = NetworkRequest.Builder()
            builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            builder.setNetworkSpecifier(
                WifiNetworkSpecifier.Builder().apply {
                    setSsid("IOTWifi")
                    setWpa2Passphrase("iotwifipassword")
                }.build()
            )
    
            try {
                connectivityManager.requestNetwork(builder.build(), object : NetworkCallback() {
                    override fun onAvailable(network: Network) {
                        connectivityManager.bindProcessToNetwork(network)
                    }
                })
            } catch (e: SecurityException) {
                Timber.e(e)
            }
    

    This will show a system dialog from which the user can (eventually) select the IoT wifi network to connect to it.

    The key to have Android maintain a connection to the network is to not unregister the network callback from ConnectivityManager. Android 10 will not automatically switch back to one with internet access until you do so.

    The user experience isn't great as the dialog title isn't too clear and because of the inexplicable delay in showing the matching WiFi network (which is already visible in old scan results). But at least a persistent connection to the IoT device on Android 10 is now possible.