Initial Investigation with Wifi-Direct

I started the work by just having two Nexus 4 phones, on having 5.0.1 Lollipop and other 4.4.4. Kitkat, soon noticed that it’s not really best configuration, since there has been loads of improvement done to the API between there versions, and some issues might not be present with all versions. Thus I did get 3 additional Nexus 5 devices for testing purposes. The firmware’s on these were, 5.0.1, 4.4.4 and 4.4.3

The API used is WifiP2pManager   which is actually a system service, thus anything done with it, will effect on any other apps utilizing the same service. Also utilization of Wi-Fi with other means, might affect the usage, but in this point. The Investigation is made having the application being the only app using the service, as well as without having any WLAN connectivity available.

First I wanted to get application which loops connection & data exchange between two devices, so the behavior would be:

  1. Discover service from other device
  2. Connect to the service on the other device
  3. Exchange data
  4. Disconnect from the other device
  5. Start discovering the service

Managed to get this working quite a well, though with Nexus 5 which was having KitKat 4.4.3, there were still loads of issues, and if that device was trying to communicate any device having KitKat on it, then the connection establishment would fail really often, and the actual successful data exchange was happening rarely (at least when compared to communications between 4.4.4 and 5.0.1 in any configuration), thus if you have KitKat 4.4.3 on your device, please update it to 4.4.4 for more pleasurable experience with the WifiP2pManager.

  1. Advertise your service

In order the other device to be able to discover the service you have, you must advertise it. To do this you simple first create an instance of WifiP2pDnsSdServiceInfo with your own service type and unique instance name, so you can identify your own services, and then use addLocalService() to add it to the WifiP2pManager.

Do remember to remove the local service when you exit, so it won’t be advertised while you are not running the service.

  1. Discover service from other device

For discovering step, we could indeed go directly discovering any devices that are having the service we are interested call discoverServices() function, and make sure the discovery is running until we get onDnsSdServiceAvailable() function from  DnsSdServiceResponseListener() called with service we are interested in. I indeed did do this first, but decided to go back to the ‘normal’ way and first discover devices as peers, and once I have found devices, then would I start service discovery. Reasoning was that, supposedly the service discovery uses more battery, but also I did find that keeping the peer discovery active & running nicely, is loads easier than having the service discovery doing the main tasks in discovery step.

Peer discovery works rather simple way, you just call discoverPeers() and once the onSuccess() is called you are happily running. And If you did register for WIFI_P2P_DISCOVERY_CHANGED_ACTION, you’ll get notification for state changes, and can react on them. And once there is changes on peers discovered (more found, or lost), you’ll be informed via WIFI_P2P_PEERS_CHANGED_ACTION, and you can simply call requestPeers() to get the latest list of peers.

There are few issues though that you might want to take care of

  1. The discovery can be started by any application at any time and WIFI_P2P_PEERS_CHANGED_ACTION events are then given to all parties, so even if you don’t start the peer discovery, you’ll be getting the events
  2. You also might get WIFI_P2P_PEERS_CHANGED_ACTION events when your logic is handling something else, for example connecting, discovering services, thus you need to have logic to take care of those situations.
  3. Note that you do get WIFI_P2P_DISCOVERY_CHANGED_ACTION with WIFI_P2P_DISCOVERY_STOPPED when you are connecting to the other device, thus if you attempt to call requestPeers(), you’ll get:
    1. BUSY error with KitKat 4.4.4 and newer firmware versions, which is good,
    2. the peer discovery started just fine, and without any errors, when using KitKat 4.4.3, this will likely mess up your connection attempt, thus should be avoided.
  4. With KitKat requestPeers() might in some random cases cause onFailure() being called with generic zero error. And to make sure your logic works, and devices are being discovered, you should consider implementing watchdog feature, which would go starting peer discovery, once it determines that it has taken too long since the last peer was discovered.

Note also that onPeersAvailable() for WifiP2pManager.PeerListListener() will be called when all peers are lost, thus for your logic, at least you should check whether there were any peers discovered before starting service discovery, of course you can also check the peer list, and determine whether you should start service discovery or continue looking for right mates. Anyhow, with my simple app, the logic just checks whether there is any peers available, and if there is, then we start querying the services strait away.

Before starting the Service discovery, you first need to create a new instance of WifiP2pManager.DnsSdServiceResponseListener() and use setDnsSdResponseListeners() to tell to the WifiP2pManager where you want the service discovery results being delivered.

The service discovery process is handled in three steps:

  1. Create a service requests by creating a new instance of WifiP2pDnsSdServiceRequest and then add it to the service by calling addServiceRequest(), and once onSuccess() callback fro the action listener is called, then move to step 2.
  2. Call discoverServices(), and if the onSuccess() is called, then just wait until the API takes you to step 3.
  3. Once there is services running in nearby devices, and they were discovered, then the onDnsSdServiceAvailable() from WifiP2pManager.DnsSdServiceResponseListener() will be called and you can check whether the service you are looking (the one you are advertising, with your own service type) for is available and then start connecting.

Then again some issues were discovered, and here’s short list of them:

  1. There were some race condition discovered earlier, thus you should have the suggested 1 second delay between addServiceRequest() and discoverServices() to avoid random NO_SERVICE_REQUESTS error
  2. With Kitkat also when using the RemoveGroup() as disconnection method, the discoverServices() will end up giving NO_SERVICE_REQUESTS really often, and only way to get rid of it, was found to be to switch off WLAN, and once the state change was reported back, then switch it back on. Not then that:
    1. I never seen this happening with Lollipop
    2. With Kitkat this often leads to local Service being lost (i.e. the discovery stopped working on the other end), thus you should clear local services before switch off, and add them back once you switch the WLAN back on
  3. With KitKat, you also randomly get generic zero error with discoverServices() call, and the suggested fix is the same as with NO_SERVICE_REQUESTS error.
  4. With KitKat calling discoverServices() when you have active service discovery already, appeared to confuse the service time-to-time, thus, would suggest cancelling any previous service discoveries, before starting new ones.
  5. With KitKat 4.4.3 sometimes the service discovery appears to not find any service, and with tests the watchdog resets the discovery after 2 minutes of idle time.
  6. Connect to the service on the other device

Once you have discovered the service from the device you want to connect to, you simply do following:

  1. Call connect() function with WifiP2pConfig instance having the device address (given you via device argument in onDnsSdServiceAvailable()). If the onSuccess() is called for the action listener, then move to step 2.
  2. Once connection is established, you’ll receive WIFI_P2P_CONNECTION_CHANGED_ACTION event. Note that this comes also if the connection failed (you get disconnection event), thus check whether the EXTRA_NETWORK_INFO indicates that you are connected, and if you are then call requestConnectionInfo().
  3. Once the information for the connection is ready, it will be delivered via ConnectionInfoListener interface. And with the WifiP2pInfo argument, you can determine whether your device is Group owner or client, and you can then act accordingly.

With the Connection establishment there were no real issues, exception being connections between KitKat 4.4.4 and 4.4.3, which with the behavior experienced was as follow:

  1. Device’s discover each other
  2. The both get right service, and start connecting to it
  3. The connection is never connected, and watchdog kicks in to re-start the discovery process.

 

  1. Exchange data

Once the onConnectionInfoAvailable() of ConnectionInfoListener is called, you can get the IP address of group owner from the WifiP2pInfo given as argument. With Client simply connect to that address, and with Group owner, you could either start listening connections in this point, or you could have had started the listening processes earlier.

No real issue noted here, though, with current tests all tests were handled in a way that the devices were stationary, and disconnections happened only when data was exchanged. In reality clients could go out of range at any moment, thus the data exchange might fail really easily.

  1. Disconnect from the other device

Currently the only reliable way from coding perspective to do disconnections has been to remove the group from the group owner’s side. This would disconnect the connection strait away in group owner’s side, and after some seconds delay also from client side.

In real world the disconnection would generally happen when devices go out of range, and with testing time, I so far simulated this by switching devices off.

Only notable issue here is that when you remove the group, the data exchange stops, and if you do the removal right after sending data, then there is really high risk that the data never arrives into the other end.

Notable thing also is that you might want to avoid disconnecting in onConnectionInfoAvailable() function, since group owner seems to get that first, thus doing group removal there, will mean that once client gets the function called, the information given will be invalid.

  1. Start discovering the service

As a last step, you just need to go back to the first step, and start the peer discovery again. In general you would do this once you get the WIFI_P2P_CONNECTION_CHANGED_ACTION and determine that you don’t have connection. You’ll get here once your connection was disconnected, or the connection request lead into situation where the connection was not established.