Search code examples
androidxamarin.androidandroid-wifiwifimanager

Android : Reconnect to Wi-Fi after entering coverage area while screen turned off


I have been struggling with automatically turning on Wi-Fi as soon as the device is within range of an access point without turning on the screen. It has been very frustrating to test and figure out a solution, especially since different devices have completely different results.

Base Test
Keep the screen turned off during this test. The application should hold a WifiLock.

  1. Walk out of WiFi coverage and stay there for a minute.
  2. Walk back into coverage.

Result : The Wifi is not reconnected immediately and thus app does not reconnect to server. Depending on the device and settings, sometimes it would not reconnect at all until the screen was turned on.

Forcing the Wi-Fi to reconnect
Ok, this time my application calls WifiManager.Reconnect() at an interval if Wifi is disconnected.

Repeated the test. Results : Worked for the S3, failed for other devices.

Tried adding some other calls
Tried different combinations of WifiManager.Scan(), WifiManager.Reassociate(), ...etc. Eventually it was working for most devices(HTC, S3) except the S4.

Code that seems to work for all devices

NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
if (!_wifiManager.IsWifiEnabled || _wifiManager.WifiState == WifiState.Disabled || _wifiManager.WifiState == WifiState.Disabling)
{
    // Make sure the Wi-Fi is enabled, required for some devices when enable WiFi does not occur immediately
    _wifiManager.SetWifiEnabled(true);
}

if (!wifiInfo.IsConnectedOrConnecting)
{
    // Do not wait for the OS to initiate a reconnect to a Wi-Fi router
    _wifiManager.PingSupplicant();
    if (_wifiManager.WifiState == WifiState.Enabled)
    {
        try
        {
            // Brute force methods required for some devices
            _wifiManager.SetWifiEnabled(false);
            _wifiManager.SetWifiEnabled(true);
        }
        catch (Java.Lang.SecurityException)
        {
            // Catching exception which should not occur on most devices. OS bug details at :
            // https://code.google.com/p/android/issues/detail?id=22036
        }
    }
    _wifiManager.Disconnect();
    _wifiManager.StartScan();
    _wifiManager.Reassociate();
    _wifiManager.Reconnect();
}

I am not even sure all this code is necessary as I was unable to find much information online. WifiFixer did help some. But this does seem to work for the devices I have tested on.

The Question

  • Is there a better way of doing this?
  • Do the manufacturers really modify the base Android where I can be seeing this much of a difference?
  • Is this completely the wrong way to approach this?

Thanks for reading through all this :)

Additional Notes

  1. Code runs during 10+ second interval initiated from the AlarmManager. WakeLock is held only for the duration of this call.
  2. Before this final scary looking solution/hack the "Wifi Sleep Policy" affected the results. This confused me since I am holding a WifiLock the entire time, which I thought was equivalent of "Never".
  3. Changing the "Wifi Sleep Policy" programmatically does not work for the S4, can anyone else confirm?
  4. Yes, we have a specific need to do this and are aware of battery implication.

Solution

  • Had a second go around in the area. While the above solution did work for all our qualified devices, there were too many calls that might have been unnecessary. Plus we got new device for which the solution did not work. Here is a much better solution:

    At every interval this code is called

    NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
    if (!wifiInfo.IsConnectedOrConnecting)
    {
        // Need to make sure the CPU does not go to sleep before the following async calls are finished
        _wifiScanWakeLock.Acquire();
    
        // Do not wait for the OS to initiate a reconnect to a Wi-Fi router
        _wifiManager.StartScan();
    }
    
    • _wifiScanWakeLock is just a partial, non-reference counted WakeLock, Dispose in OnDestroy

    When the Wi-Fi scan is finished

    private void OnWifiScanResultsReceived(Intent result)
    {
        NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
        if (!wifiInfo.IsConnectedOrConnecting)
        {
            Dictionary<string, int> savedNetworks = new Dictionary<string, int>();
            foreach (WifiConfiguration config in _wifiManager.ConfiguredNetworks)
            {
                string escapedSsid = Regex.Replace(config.Ssid, "^\"|\"$", String.Empty);
                savedNetworks[escapedSsid] = config.NetworkId;
            }
    
            foreach (ScanResult ap in _wifiManager.ScanResults)
            {
                int networkId;
                if (savedNetworks.TryGetValue(ap.Ssid, out networkId))
                {
                    savedNetworks.Remove(ap.Ssid);
                    _wifiManager.EnableNetwork(networkId, false);
                }
            }
        }
        _wifiScanWakeLock.Release();
    }
    
    • BSSID for the WifiConfiguration is always null and cannot be used that to uniquely compare to the BSSID of the ScanResult
    • This is the core code, obviously you would have to worry about case of two identical SSIDs and other optimizations