WiFi direct service discovery in android

wi-fi-direct-service-discovery

I suggest reading my previous posts on NSD and WiFi Direct before reading this one. This post requires some of things discussed in these two posts. If you are already aware of WiFi direct service discovery, you  can directly check out my sample code on git.

Like NSD, we can register and discover services over WiFi direct. The problem I faced with WiFi direct was one prefixed port for initial data transfer (as peer devices were not aware of the port information), after that the port was dynamic, and fixed port was released. Using WiFi direct service discovery we can append additional data (100-200 bytes)  with the advertised service. So unlike WiFi direct, we can request port dynamically and append it with service. No prefixed port.

We all know socket communication, here is a recap just in case:

//Server side
ServerSocket mServer = new ServerSocket(mPort);
Socket socket = null;
while (acceptRequests) {
    // this is a blocking operation
    socket = mServer.accept();
    handleData(socket);
}

//Client side
socket = new Socket(hostIP, hostPort);
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(transferObject);
oos.close();

If you have read my earlier post on WiFi direct, you know almost everything required for WiFi direct service discovery (except appending additional data part). Function is similar to NSD and code is similar to WiFi-Direct.

Adding local service

Same class as WiFi direct is used. Here we use WifiP2pmanager‘s addLocalService() method. Here is the official documentation for this method:

wifip2pmanager_addlocalservice

All the parameters are familiar except for WifiP2pServiceInfo. Similar to NSD, this is a holder object for service information. In this case it is a WiFi P2P service info.

WifiP2pServiceInfo is a class for storing service information that is advertised over a WiFi peer-to-peer setup. It has two direct sub-classes. Both are bonjour service info:

For our use case, which sharing of port and IP information and then finally sharing data with other devices, we will use the former, it allows us to append a string map with the service. The later allows us to append a list of string, which can also be used.

As mentioned earlier, WifiP2pDnsSdServiceInfo allows us to append a string map. here is how to do it.

Map<String, String> record = new HashMap<String, String>();
record.put(KEY_BUDDY_NAME, player == null ? Build.MANUFACTURER : player);
record.put(KEY_PORT_NUMBER, String.valueOf(port));
record.put(KEY_DEVICE_STATUS, "available");
record.put(KEY_WIFI_IP, Utility.getWiFiIPAddress(context));

WifiP2pDnsSdServiceInfo service = WifiP2pDnsSdServiceInfo.newInstance(
    SERVICE_INSTANCE, SERVICE_TYPE, record);
wifiP2pManager.addLocalService(wifip2pChannel, service, new WifiP2pManager.ActionListener() {

    @Override
    public void onSuccess() {
        Log.d(TAG, "Added Local Service");
    }

    @Override
    public void onFailure(int error) {
        Log.e(TAG, "ERRORCEPTION: Failed to add a service");
    }
});

As you can see in the sample code above, a map called record is passed when creating instance of service info object. After this you are done with advertising your WiFi direct service.

Discovering WiFi direct services

Discovery requires adding a service discovery request in WiFi direct via WifiP2pManager‘s addServiceRequest() method. Here is description from the official site:

wifip2pmanager_addservicerequest

After this a the service discovery request must be issued. Set the type of service you want to discover DNS-SD or UPNP and issue the request.

serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
wifiP2pManager.addServiceRequest(wifip2pChannel, serviceRequest,
new WifiP2pManager.ActionListener() {

    @Override
    public void onSuccess() {
        Log.d(TAG, "Added service discovery request");
    }

    @Override
    public void onFailure(int arg0) {
        Log.d(TAG, "ERRORCEPTION: Failed adding service discovery request");
    }
});
wifiP2pManager.discoverServices(wifip2pChannel, new WifiP2pManager.ActionListener() {

	@Override
	public void onSuccess() {
		Log.d(TAG, "Service discovery initiated");
	}

	@Override
	public void onFailure(int arg0) {
		Log.d(TAG, "Service discovery failed: " + arg0);
	}
});

There is a reason code associated with every failure callback. Check those error code if you get a failure callback.

The map that was advertised is received in DnsSdTxtRecordListener interface callback. This needs to be set to receive the map. And DnsSdServiceResponseListener is callback for receiving the advertised service. Here is how to set it:

wifiP2pManager.setDnsSdResponseListeners(wifip2pChannel,
new WifiP2pManager.DnsSdServiceResponseListener() {

    @Override
    public void onDnsSdServiceAvailable(String instanceName,
        String registrationType, WifiP2pDevice srcDevice) {

        // A service has been discovered. Is this our app?
        if (instanceName.equalsIgnoreCase(SERVICE_INSTANCE)) {
            // yes it is
         } else {
            //no it isn't
        }
    }
}, new WifiP2pManager.DnsSdTxtRecordListener() {

    @Override
    public void onDnsSdTxtRecordAvailable(
            String fullDomainName, Map<String, String> record,
            WifiP2pDevice device) {
        boolean isGroupOwner = device.isGroupOwner();
        peerPort = Integer.parseInt(record.get(TransferConstants.KEY_PORT_NUMBER).toString());
			// further process
    }
});

here in the text record callback you can see the map is received with whatever info was set. After this it is same as WiFi direct. you can check out my earlier post for this.

You need to have a connection info listener same as WiFi direct example (old post), and you need to connect with the device, with info received as a part of DnsSdTxtRecordAvailable or DnsSdServiceAvailable callbacks (above code example).

In the code example above you can see that the port information is received as a part of string map in text record available method, and in connection info callback you can get the IP address of the group owner of the current WiFi group. After that, its the same age old socket communication.

Check out the google sample for this. My sample app source is available on git.

Happy coding!!!

Let me know what you think. Got any questions? shoot below in comments.

-Kaushal D (@drulabs twitter/github)

drulabs@gmail.com