Investigate our options for connecting to a wifi hotspot (which could be a WFD legacy mode hotspot) as a wifi client, rather than by using the WFD API.
The APIs available for connecting to wifi networks have changed a lot over the years, so we'll need to test on a lot of different API versions.
Questions:
Can we scan for available networks? How often? What permissions are needed? Can we access the SSIDs of the networks?
Can we request a connection to a network that's not currently in range? If so, do we connect automatically when the network comes in range? What if the client's screen is off? Is the connection kept if the network doesn't have internet access?
If two or more devices are providing hotspots with the same SSID and password, can a client roam from one to the other?
If a client has previously connected to a hotspot and accepted the dialog that warns about no internet access, is the dialog shown again when reconnecting to the same hotspot? Is it shown again when connecting to a different device's hotspot that uses the same SSID and password?
The "create group" menu item in the wifi-direct-rebased branch could be useful for these experiments: it creates a WFD legacy mode hotspot with a hardcoded SSID and password.
However, per the WifiManager docs, the addNetwork() method used by this hack was deprecated in API 29 and doesn't work for apps targetting API 29 or higher. So we would need to adapt the hack to use addNetworkSuggestions() instead.
API for saving wifi networks on Android 11, although this is intended for networks with internet access, so we can still expect the device to complain if the network doesn't have internet access:
The wifi-client-request-network branch uses three different techniques for requesting a connection to a specific wifi network:
API >= 29 (tested on Samsung A21s and Nokia 1.3): The app uses WifiNetworkSpecifier, as recommended here. When the app requests the network, the system shows a progress indicator and searches for the network nearby. If the network isn't found, the system shows a dialog with options to try again or cancel. If the network is found, the system shows a dialog asking the user whether to connect or cancel. The app receives a callback with the user's response. The requested network does not appear in the device's list of saved networks. If the device is already connected to a wifi network with internet access, it disconnects and connects to the requested network instead. As expected, there is no warning about lack of internet access and the device stays connected to the network. The app can disconnect from the requested network by unregistering the NetworkCallback. After doing this, reconnecting to the requested network requires confirmation from the user again. The docs seem to suggest that if the app doesn't unregister the callback then the app can reconnect to the requested network in future without needing user approval again, but I haven't tested this yet.
API < 29 (tested on Nexus 5X): The app creates a WifiConfiguration with a high priority value, adds the configuration via WifiManager#addNetwork(), and enables the configuration via WifiManager#enableNetwork(). This doesn't require user approval and connects to the requested network immediately, even if the device is currently connected to a network with internet access and the requested network doesn't have internet access. The requested network is added to the device's list of saved networks. There is no warning about lack of internet access and the device stays connected to the network. The app can disconnect from the requested network and remove the saved configuration by calling WifiManager#removeNetwork().
API < 29 (tested on Nexus 5X): As in the previous method, the app creates a WifiConfiguration with a high priority value and adds the configuration via WifiManager#addNetwork(). The app then uses reflection to call WifiManager#connect(). Like the previous method, this connects to the requested network immediately, even if the device is currently connected to a network with internet access and the requested network does not have internet access. The requested network is added to the device's list of saved networks. There is no warning about lack of internet access and the device stays connected to the network. The app can disconnect from the requested network and remove the saved configuration by calling WifiManager#removeNetwork().
All of these methods, especially the last two, should be tested on a wider range of devices.
Methods 2 and 3 both work on the Moto G 4G and Honor 8A. A few seconds after connecting to the requested network, the Honor 8A shows a dialog that warns about lack of internet access. Regardless of whether "Cancel" or "Connect" is chosen, the device stays connected to the requested network.
Both devices will disconnect from a network with internet access when asked to connect to the requested network. But the Honor 8A sometimes reconnects to the network with internet access instead of connecting to the requested network. This happens with methods 2 and 3, and it doesn't seem to make a difference whether "Cancel" or "Connect" was chosen in the warning dialog at the previous attempt.
TCP connection attempts from the client devices to the device providing the hotspot (Nokia 1.3) can take several seconds to connect, which is surprising on a LAN.
The docs seem to suggest that if the app doesn't unregister the callback then the app can reconnect to the requested network in future without needing user approval again, but I haven't tested this yet.
I haven't been able to reproduce the documented behaviour (tested with the Samsung A21s as client and the Nokia 1.3 providing a legacy mode hotspot).
There doesn't seem to be a way to disconnect from the requested network without calling ConnectionManager#unregisterNetworkCallback(), which (per the docs and confirmed by testing) disconnects and forgets the network, thus requiring user confirmation for reconnection. WifiManager#disconnect() returns false, while WifiManager#getConnectionInfo() returns a WifiInfo with its network ID set to -1, so WifiManager#disableNetwork() can't be used either.
If the hotspot is stopped and restarted, thus forcing the client to disconnect, the client asks for user confirmation again when reconnecting, even if the same NetworkRequest and NetworkCallback instances are used. The client asks for user confirmation even if it's currently connected to the requested network.
On older devices (Moto G 4G and Honor 8A) where we use the WifiManager API to request the network, WifiManager#disconnect() can be used to disconnect from the network without forgetting it. WifiManager#disableNetwork() also works, and is a better choice as it prevents the device from reconnecting automatically to the saved network when the app's not running.
Compared with forgetting the network, disabling it has the advantage that the lack of internet access warning only has to be accepted once. After that the user's choice is remembered and the warning isn't shown again (tested on the Honor 8A).
On some devices (eg Moto G 4G) the saved network is left on the device when the app is uninstalled, while on others (eg Honor 8A) it's removed. This probably depends on the API change that associates wifi networks with the apps that created them, as the Honor 8A also shows which app created the saved network.
On API >= 29 we can either use the WFD API to connect to a hotspot, which requires location permission but not user confirmation, or the ConnectionManager API, which requires user confirmation each time but not location permission.
On API < 29 we can use the WifiManager API, which shows a warning about lack of internet access on some devices. Once the user has accepted the warning once, it's not shown again, and ignoring or dismissing the warning doesn't seem to affect connectivity.
All of this needs to be tested on a wider range of devices.
The Huawei P8 Lite 2015, Alcatel A3 XL and Moto Play E6 can all disconnect from a network with internet access, connect to the requested network and remain connected without showing a warning about lack of internet access. Like the Honor 8A, the P8 Lite sometimes reconnects to the network with internet access instead of connecting to the requested network.
Reflection hacks don't appear to improve the success rate on any of the devices tested so far.
Three devices running API >= 29 (Redmi Note 7, Huawei Y6P and Samsung A10s) can all disconnect from a network with internet access, connect to the requested network (with user confirmation) and remain connected without showing a warning about lack of internet access. The Redmi Note 7 needed three attempts to find the network. All three devices were also able to connect to the network without user confirmation via the WFD API.
Can we request a connection to a network that's not currently in range?
API < 29: Yes. Tested on the Moto E6 Play and Honor 8A.
API >= 29: Not via the WifiManager API. Tested on the Samsung A21s, but results should be the same on other devices - the system shows a retry/cancel dialog if the requested network isn't found.
If so, do we connect automatically when the network comes in range? What if the client's screen is off?
API < 29: Yes, even if the screen is off. Tested on the Moto E6 Play and Honor 8A.
API >= 29: N/A because we can't request a network that's not in range.
Is the connection kept if the network doesn't have internet access?
See comments above.
API < 29: Yes, but some devices show a warning dialog, and some will prefer a network with internet access. Tested on the Moto G 4G, Huawei P8 Lite 2015, Alcatel A3 XL, Moto Play E6 and Honor 8A.
API >= 29: Yes, but the user has to approve the requested network each time.
Can we request a connection to a network that's not currently in range?
API >= 29: With the WFD API this works if the group comes into range within a few seconds. If it takes too long (e.g. 1 minute) for the group to come into range then attempt seems to time out and the client needs to call WifiP2pManager#connect() again. Tested with the Samsung A21s as client and Nokia 1.3 as hotspot, and vice versa.
On both devices, the connect() call receives an immediate success callback regardless of whether the hotspot is within range. If the hotspot comes into range quickly enough then the group info is received. If not, there's a CONNECTION_STATE_CHANGE broadcast about 10 to 15 seconds after the connect() call that may indicate that the attempt has timed out.
If two or more devices are providing hotspots with the same SSID and password, can a client roam from one to the other?
API < 29: Not if a network with internet access is in range. When the first hotspot goes out of range, the client will switch to the network with internet access. On the Honor 8A this happens immediately, while on the Moto G 4G it takes about five minutes. When the second hotspot comes into range, the client remains connected to the network with internet access, even after the second hotspot has been in range for several minutes.
On the Moto G 4G this appears to be a matter of policy - the wifi network list says "No Internet Access Detected, won't automatically reconnect" for the hotspot network.
On the Honor 8A it's not clear whether it's a matter of policy, but the hotspot network has an icon indicating that the network doesn't have internet access.
When there's no network with internet access in range, the client still won't roam to the second hotspot while the screen is turned off. On the Honor 8A, turning the screen on is enough to cause the client to connect to the second hotspot (perhaps because turing the screen on triggers a wifi scan?). In the wifi network list, the hotspot network has an icon indicating that the network doesn't have internet access, but the client reconnects to it anyway.
This is not the case on the Moto G 4G. Turning the screen on doesn't cause the client to connect to the second hotspot, and neither does opening the wifi network list (which definitely triggers a wifi scan, even if turning the screen on doesn't do so). As mentioned above, this seems to be a matter of policy - the network is marked as "won't automatically reconnect".
This requires further testing on API < 29 to determine whether the difference between the Honor 8A and the Moto G 4G is model-specific, manufacturer-specific and/or version-specific.
Like the Honor 8A, the P8 Lite sometimes reconnects to the network with internet access instead of connecting to the requested network.
Quick side note on this issue: triggering a wifi scan causes the P8 Lite 2015 to disconnect from the network with internet access and connect to the requested network. (The wifi network list on the P8 Lite 2015 has a button that triggers a scan - opening the list doesn't automatically trigger a scan.) This suggests that when clients reconnect to the network with internet access instead of connecting to the requested network, it could be because they haven't yet detected the requested network. We might be able to influence this by triggering a scan after adding the network, although app-triggered scans are throttled on newer API versions.
This requires further testing on API < 29 to determine whether the difference between the Honor 8A and the Moto G 4G is model-specific, manufacturer-specific and/or version-specific.
When a network with internet access was available, the P8 Lite 2015 and Moto E6 Play both connected to the network with internet access when the first hotspot went out of range. Both devices remained connected to the network with internet access when the second hotspot came in range.
The P8 Lite subsequently went into deep sleep and dropped its connection to the network with internet access. When the screen was turned on, the P8 Lite connected to the second hotspot. It's not clear whether this was a matter of policy or whether the second hotspot just happened to be detected before the network with internet access.
The E6 Play, on the other hand, remained connected to the network with internet access even after a wifi scan was triggered by opening the wifi network list.
When no network with internet access was available, both devices connected to the second hotspot when it came in range (while the devices' screens were turned off). The P8 Lite didn't appear to drop its connection to the network during sleep in this test, unlike the previous test, and the screen didn't need to be turned on to trigger the connection to the second hotspot.
Summary so far for API < 29:
When a network with internet access is available, clients will switch to it when the first hotspot goes out of range and may remain connected to it when the second hotspot comes in range. On the Moto G 4G it's a matter of policy not to reconnect automatically to networks without internet access. On other devices (e.g. P8 Lite 2015), it may depend on whether the client has dropped its connection to the network with internet access during sleep, giving the second hotspot a chance to be chosen when the device wakes up
When no network with internet access is available, clients may connect to the second hotspot automatically when it comes in range. The Moto G 4G won't do this as a matter of policy. On other devices (e.g. Honor 8A), it may not happen until a wifi scan is triggered