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.

Advertisements

4 Comments »

  1. What do you mean by “where is the rest of it”? What more do you need?

    I have issues with android NSD too but different ones… bugs and and unrecoverable crashes. Looks like I might use your posts to implement it in java myself.

    Comment by Marcin — September 23, 2014 @ 5:37 pm

    • There’s no TXT record.

      The NsdServiceInfo class getTxtRecord method always returns null because the code in com.android.server.NsdService doesn’t set it when it gets the resolution result

      Comment by Simon Lewis — September 23, 2014 @ 7:11 pm

      • Wow, that’s true. I didn’t even notice because I started using JmDNS as the android implementation is very buggy. That makes it even less useful. Why did they even bother adding it?
        Everyone seems to be using JmDNS, it mostly works and supports TXT data but has reliability problems (i.e. not reporting services that exist and can be seen by other receivers). Do you know of any alternative in android / java land? Do I really have to build it from scratch?

        Comment by Marcin — September 23, 2014 @ 9:07 pm

  2. I am trying to implement a PrintService. I am using Network Service Discovery (NSD) to find the printers (c.f. https://developer.android.com/training/connect-devices-wirelessly/nsd.html) Both the NSD and the NsdManager.ResolveListener make asynchonous calls. However, the system calls my onStartPrinterDiscovery() before the NSD functions execute. I don’t see any other opportunity to call addPrinters().

    How can this work?

    Comment by rre2016 — November 23, 2016 @ 3:30 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: