Just An Application

October 29, 2013

Service Discovery in Android And iOS – Part One: The android.net.nsd.NsdManager Class Considered Not Very Useful At All

Suppose I was writing an Android program and for some strange reason I wanted to automatically find any printers on the local network that support the Internet Printing Protocol (IPP).

Quite why I would want to do this is anyone’s guess. Perhaps I want to do something bizarre like support printing from
my program.

Anyway, in theory I am in luck because I can use an instance of the android.net.NsdManager class which supports
mDNS based service discovery.

1.0 Getting An NsdManager Instance

An instance of android.net.nsd.NsdManager can be obtained by a call to the Context.getSystemService
method with the argumentcContext.NSD_SERVICE.

So in an Application’s main Activity we can do something like this

    NsdManager manager = (NsdManager)getSystemService(NSD_SERVICE);

2.0 Starting Service Discovery

The process of service discovery is started by invoking the NsdManager.discoverServices method which is defined like this

    public void discoverServices(String serviceType, int protocolType, NsdManager.DiscoveryListener listener)

The methods defined by the DiscoveryListener instance passed as the listener argument are invoked asynchronously.

3.0 android.net.nsd.NsdManager.DiscoveryListener

The DiscoveryListener interface defines two methods to report the outcome of starting the process of service discovery

    public void onDiscoveryStarted(String serviceType)

    public void onStartDiscoveryFailed(String serviceType, int errorCode)

two methods to report the outcome of stopping the process of service discovery

    public void onDiscoveryStopped(String serviceType)

    public void onStopDiscoveryFailed(String serviceType, int errorCode)

a method to report that a service has been discovered

    public void onServiceFound(NsdServiceInfo serviceInfo)

and a method to report that a previously discovered service has disappeared.

    public void onServiceLost (NsdServiceInfo serviceInfo)

4.0 Resolving A Service

If the onServiceFound method of the DiscoveryListener instance passed to the call to discoverServices is invoked then a service has been discovered.

We can then call the NsdManager.resolveService which is declared like this

    public void resolveService(NsdServiceInfo serviceInfo, NsdManager.ResolveListener listener)

The serviceInfo argument is NsdServiceInfo instance passed to onServiceFound.

The methods defined by the ResolveListener instance passed as the listener argument are invoked asynchronously.

5.0 android.net.nsd.NsdManager.ResolveListener

The ResolveListener interface declares a method to report that a service has been resolved

    public void onServiceResolved(NsdServiceInfo serviceInfo)

and a method to report that it was not possible to resolve a service

    public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode)

6.0 The FindServicesNSD Class

Here is the source code for the FindServicesNSD class which encapsulates the use of the NsdManager service
discovery methods

    // FindServicesNSD.java
    
    // Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
    
    package xper.villiars;
    
    import android.net.nsd.NsdManager;
    import android.net.nsd.NsdManager.DiscoveryListener;
    import android.net.nsd.NsdManager.ResolveListener;
    import android.net.nsd.NsdServiceInfo;
    import android.util.Log;
    
    final class FindServicesNSD
                implements
                    DiscoveryListener,
                    ResolveListener
    {
        // DiscoveryListener
	
        @Override
        public void onDiscoveryStarted(String theServiceType)
        {
            Log.d(TAG, "onDiscoveryStarted");
        }
    
        @Override
        public void onStartDiscoveryFailed(String theServiceType, int theErrorCode)
        {
            Log.d(TAG, "onStartDiscoveryFailed(" + theServiceType + ", " + theErrorCode);
        }
	
        @Override
        public void onDiscoveryStopped(String serviceType)
        {
            Log.d(TAG, "onDiscoveryStopped");
        }
	
        @Override
        public void onStopDiscoveryFailed(String theServiceType, int theErrorCode)
        {
            Log.d(TAG, "onStartDiscoveryFailed(" + theServiceType + ", " + theErrorCode);
        }
	
        @Override
        public void onServiceFound(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceFound(" + theServiceInfo + ")");
            Log.d(TAG, "name == " + theServiceInfo.getServiceName());
            Log.d(TAG, "type == " + theServiceInfo.getServiceType());
            serviceFound(theServiceInfo);
        }
    
        @Override
        public void onServiceLost(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceLost(" + theServiceInfo + ")");
        }
    
        // Resolve Listener
    
        @Override
        public void onServiceResolved(NsdServiceInfo theServiceInfo)
        {
            Log.d(TAG, "onServiceResolved(" + theServiceInfo + ")");
            Log.d(TAG, "name == " + theServiceInfo.getServiceName());
            Log.d(TAG, "type == " + theServiceInfo.getServiceType());
            Log.d(TAG, "host == " + theServiceInfo.getHost());
            Log.d(TAG, "port == " + theServiceInfo.getPort());
	    }
	
        @Override
        public void onResolveFailed(NsdServiceInfo theServiceInfo, int theErrorCode)
        {
            Log.d(TAG, "onResolveFailed(" + theServiceInfo + ", " + theErrorCode);
        }
    
        //
	
        FindServicesNSD(NsdManager theManager, String theServiceType)
        {
            manager     = theManager;
            serviceType = theServiceType;
        }
	
        void run()
        {
            manager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, this);
        }
	
        private void serviceFound(NsdServiceInfo theServiceInfo)
        {
            manager.resolveService(theServiceInfo, this);
        }
	
        //
	
        private NsdManager   manager;
        private String       serviceType;
	
        //
	
        private static final String TAG = "FindServicesNSD";
    }

7.0 Example

Running an instance of FindServicesNSD created like this

    new FindServicesNSD((NsdManager)getSystemService(NSD_SERVICE), "_ipp._tcp")

on a device on a network with an IPP capable printer present results in the following


    D/FindServicesNSD( 2459): onDiscoveryStarted
    D/FindServicesNSD( 2459): onServiceFound(name:\
        Canon MG6200 seriestype: _ipp._tcp.host: nullport: 0txtRecord: null)
    D/FindServicesNSD( 2459): name == Canon MG6200 series
    D/FindServicesNSD( 2459): type == _ipp._tcp.
    D/FindServicesNSD( 2459): onServiceResolved(name:\
        Canon32MG620032seriestype: ._ipp._tcphost: /10.0.1.5port: 631txtRecord: null)
    D/FindServicesNSD( 2459): name == Canon32MG620032series
    D/FindServicesNSD( 2459): type == ._ipp._tcp
    D/FindServicesNSD( 2459): host == /10.0.1.5
    D/FindServicesNSD( 2459): port == 631

This is output from adb logcat run through grep FindSevicesNSD then slightly edited for clarity.

8.0 android.net.nsd.NsdServiceInfo: Where’s The Rest Of It ?

Given the instance of NsdServiceInfo passed to the onServiceResolved method it is possible to determine, the name and type of the service, the host on which it is running and the port it is listening on.

There is however additional per service information available via the mDNS service discovery mechanism which is not made accessible.[1]

While it may be possible to use a service without the missing information, this is not guaranteed to be the case

Using a service supporting IPP is one example where at least one piece of the missing per-service information is necessary.

9.0 Conclusion

Although the NsdManager class is documented as supporting mDNS based service discovery the deficiency in the implementation means that it ends up being not very useful at all.

Notes

  1. They could have made it accessible, its just that they had it and then they threw it away !


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.