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
-
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.