Wifi P2P service discovery works intermittently

Soumya Das picture Soumya Das · Oct 10, 2014 · Viewed 8.3k times · Source

Wifi P2P service discovery is not behaving as expected. I am seeing intermittent issues where the DNSSD listeners are not called always and hence I have no clue of nearby devices running the same app. I am using the following two APIs - one to register a service to be discovered by other devices and the other to discover the nearby services running on other devices. Any idea if I am doing anything wrong here or is there some specific sequence of other android API calls that need to be made before I call these APIs to ensure that the listeners are always called whenever there is a new service registered or even if a service is registered before we call the API to discover the local services.

API to register a local service:

private void registerService() {
    Map<String, String> values = new HashMap<String, String>();
    values.put("name", "Steve");
    values.put("port", "8080");
    WifiP2pServiceInfo srvcInfo = WifiP2pDnsSdServiceInfo.newInstance(mMyDevice.deviceName, "_http._tcp", values);

    manager.addLocalService(channel, srvcInfo, new WifiP2pManager.ActionListener() {

        @Override
        public void onSuccess() {
            Toast.makeText(WiFiDirectActivity.this, "Local service added successfully", 
                Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onFailure(int reasonCode) {
            Toast.makeText(WiFiDirectActivity.this, "Local service addition failed : " + reasonCode,
                    Toast.LENGTH_SHORT).show();
        }
    });
}

API to discover local services:

public void discoverService() {

    manager.clearServiceRequests(channel, null);

    DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
        @Override
        /* Callback includes:
         * fullDomain: full domain name: e.g "printer._ipp._tcp.local."
         * record: TXT record data as a map of key/value pairs.
         * device: The device running the advertised service.
         */
        public void onDnsSdTxtRecordAvailable(String fullDomain, Map record, WifiP2pDevice device) {
            Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
        }
    };

    DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
        @Override
        public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice resourceType) {
            Log.d(TAG, "onBonjourServiceAvailable " + instanceName);
        }
    };

    manager.setDnsSdResponseListeners(channel, servListener, txtListener);

    WifiP2pDnsSdServiceRequest serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
    manager.addServiceRequest(channel, serviceRequest, new ActionListener() {

        @Override
        public void onSuccess() {
            // Success!
            Log.d(TAG, "addServiceRequest success");
        }

        @Override
        public void onFailure(int code) {
            // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
            Log.d(TAG, "addServiceRequest failure with code " + code);
        }

    });
    manager.discoverServices(channel, new ActionListener() {

        @Override
        public void onSuccess() {
            // Success!
            Log.d(TAG, "discoverServices success");
        }

        @Override
        public void onFailure(int code) {
            // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
            if (code == WifiP2pManager.P2P_UNSUPPORTED) {
                Log.d(TAG, "P2P isn't supported on this device.");
            } else {
                Log.d(TAG, "discoverServices failure");
            }
        }
    });
}

Note: manager & channel are initialized as

WifiP2pManager manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
Channel channel = manager.initialize(this, getMainLooper(), null);

Answer

Bartek Lipinski picture Bartek Lipinski · Jul 26, 2015

WifiP2p (in general):

Some time ago I was developing an application with a pretty complex network connectivity system based on WifiP2p with Service Broadcasting/Discovery. And based on that experience I already wrote few posts here on SO about how difficult, wearing and problematic that is. Here are two of them (they are quite full of the inside knowledge I acquired about WifiP2p with Service Discovery, and WifiP2p itself):

Why is discovering peers for Android WifiDirect so unreliable

Wi-fi P2P. Inform all peers available of some event

I would advise you to read both of my answers (even though they are focused a bit more on the WifiP2p itself). They should give you some perspective on the things you should be looking for when working with the WifiP2p Service Discovery. I can easily say that if you want to build an efficient, relatively reliable and robust WifiP2p connection system (especially with Service Discovery), you will have to work your ass off.

WifiP2p Service Discovery:

To better answer your exact question, I will tell you what I did (different from you) to make my Service Discovery work pretty reliably.

1. Broadcasting Service:

First of all: before registering your Service (with addLocalService method) you should use the WifiP2pManager's clearLocalServices method. And it is important, that you should only call addLocalService if the listener passed in the clearLocalServices returned with the onSuccess callback.

Although this sets up the broadcasting pretty nicely, I found that other nodes were not always able to detect the broadcasted service (especially when those nodes weren't already actively detecting services at the moment of registering your local Service - but they "joined" later). I couldn't find a way to fix this issue 100% reliably. And believe me I was trying probably everything WifiP2p-related. And no, the clearLocalServices-addLocalService sequence wasn't really giving satisfying results. Or more so: doing something different was working much better. What I decided to do, was after I successfully added local service (onSuccess callback from addLocalService), I started a Thread that would periodically call WifiP2pManager's method discoverPeers. That seemed to be forcing to rebroadcast all the service information.

So... basically the base of your broadcasting code should look more-less like this (bare in mind that every single piece of code I will post here is stripped-off of all "checks" if the network connectivity system is in the right state, you should design them yourself to fit your solution the best):

public void startBroadcastingService(){
    mWifiP2pManager.clearLocalServices(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
        @Override
        public void onSuccess() {
            mWifiP2pManager.addLocalService(mWifiP2pChannel, mWifiP2pServiceInfo,
                    new WifiP2pManager.ActionListener() {

                        @Override
                        public void onSuccess() {
                            // service broadcasting started
                            mServiceBroadcastingHandler
                                    .postDelayed(mServiceBroadcastingRunnable,
                                            SERVICE_BROADCASTING_INTERVAL);
                        }

                        @Override
                        public void onFailure(int error) {
                            // react to failure of adding the local service
                        }
                    });
        }

        @Override
        public void onFailure(int error) {
            // react to failure of clearing the local services
        }
    });
}

where the mServiceBroadcastingRunnable should be:

private Runnable mServiceBroadcastingRunnable = new Runnable() {
    @Override
    public void run() {
        mWifiP2pManager.discoverPeers(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onFailure(int error) {
            }
        });
        mServiceBroadcastingHandler
                .postDelayed(mServiceBroadcastingRunnable, SERVICE_BROADCASTING_INTERVAL);
    }
};

2. Discovering Service:

For the discovering of your service I used similar approach. Both with the setting up the discovering, and with trying to force "rediscovery" of services.

Setting up was performed with the sequence of the following three WifiP2pManager's methods:

removeServiceRequest, addServiceRequest, discoverServices

They were called in this exact order and a particular method (second or the third one to be exact) has been called only after the previous one had "returned" with the onSuccess callback.

The rediscovery of services was being performed with the intuitive method (just by repeating the mentioned sequence: removeServiceRequest -> addServiceRequest -> discoverServices).

The base of my code looked more-less like this (to start Service Discovery I would first call prepareServiceDiscovery() and then startServiceDiscovery()):

public void prepareServiceDiscovery() {
    mWifiP2pManager.setDnsSdResponseListeners(mWifiP2pChannel,
            new WifiP2pManager.DnsSdServiceResponseListener() {

                @Override
                public void onDnsSdServiceAvailable(String instanceName,
                                                    String registrationType, WifiP2pDevice srcDevice) {
                    // do all the things you need to do with detected service
                }
            }, new WifiP2pManager.DnsSdTxtRecordListener() {

                @Override
                public void onDnsSdTxtRecordAvailable(
                        String fullDomainName, Map<String, String> record,
                        WifiP2pDevice device) {
                    // do all the things you need to do with detailed information about detected service
                }
            });

    mWifiP2pServiceRequest = WifiP2pDnsSdServiceRequest.newInstance();
}

private void startServiceDiscovery() {
    mWifiP2pManager.removeServiceRequest(mWifiP2pChannel, mWifiP2pServiceRequest,
            new WifiP2pManager.ActionListener() {
                @Override
                public void onSuccess() {
                    mWifiP2pManager.addServiceRequest(mWifiP2pChannel, mWifiP2pServiceRequest,
                            new WifiP2pManager.ActionListener() {

                                @Override
                                public void onSuccess() {
                                    mWifiP2pManager.discoverServices(mWifiP2pChannel,
                                            new WifiP2pManager.ActionListener() {

                                                @Override
                                                public void onSuccess() {
                                                    //service discovery started

                                                    mServiceDiscoveringHandler.postDelayed(
                                                            mServiceDiscoveringRunnable,
                                                            SERVICE_DISCOVERING_INTERVAL);
                                                }

                                                @Override
                                                public void onFailure(int error) {
                                                    // react to failure of starting service discovery
                                                }
                                            });
                                }

                                @Override
                                public void onFailure(int error) {
                                    // react to failure of adding service request
                                }
                            });
                }

                @Override
                public void onFailure(int reason) {
                    // react to failure of removing service request
                }
            });
}

the mServiceDiscoveringRunnable was just:

private Runnable mServiceDiscoveringRunnable = new Runnable() {
    @Override
    public void run() {
        startServiceDiscovery();
    }
};

All this made my system work quite well. It wasn't perfect yet, but with the lack of documentation on this subject I think I couldn't do much more to improve it.

If you test this approach, be sure to tell me how it works for you (or if it works for you ;) ).