Open network service discovery API

Add support for DNS based network service discovery API. This
allows applications to discover and resolve applications on a
local network such as Wi-Fi

Change-Id: Ie89895edd35d12b7f7a23fb5fed36cb2b2079f7a
This commit is contained in:
Irfan Sheriff
2012-04-13 12:15:41 -07:00
parent 287e63bbb6
commit 18da1320fe
3 changed files with 281 additions and 93 deletions

View File

@@ -22,8 +22,8 @@ import android.os.Parcel;
import java.net.InetAddress;
/**
* Defines a service based on DNS service discovery
* {@hide}
* A class representing service information for network service discovery
* {@see NsdManager}
*/
public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
@@ -40,56 +40,63 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
public DnsSdServiceInfo() {
}
/** @hide */
public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
mServiceName = sn;
mServiceType = rt;
mTxtRecord = tr;
}
/** Get the service name */
@Override
/** @hide */
public String getServiceName() {
return mServiceName;
}
/** Set the service name */
@Override
/** @hide */
public void setServiceName(String s) {
mServiceName = s;
}
/** Get the service type */
@Override
/** @hide */
public String getServiceType() {
return mServiceType;
}
/** Set the service type */
@Override
/** @hide */
public void setServiceType(String s) {
mServiceType = s;
}
/** @hide */
public DnsSdTxtRecord getTxtRecord() {
return mTxtRecord;
}
/** @hide */
public void setTxtRecord(DnsSdTxtRecord t) {
mTxtRecord = new DnsSdTxtRecord(t);
}
/** Get the host address. The host address is valid for a resolved service. */
public InetAddress getHost() {
return mHost;
}
/** Set the host address */
public void setHost(InetAddress s) {
mHost = s;
}
/** Get port number. The port number is valid for a resolved service. */
public int getPort() {
return mPort;
}
/** Set port number */
public void setPort(int p) {
mPort = p;
}
@@ -147,5 +154,4 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
return new DnsSdServiceInfo[size];
}
};
}

View File

@@ -24,29 +24,110 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.Messenger;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
/**
* The Network Service Discovery Manager class provides the API for service
* discovery. Service discovery enables applications to discover and connect with services
* on a network. Example applications include a game application discovering another instance
* of the game application or a printer application discovering other printers on a network.
* The Network Service Discovery Manager class provides the API to discover services
* on a network. As an example, if device A and device B are connected over a Wi-Fi
* network, a game registered on device A can be discovered by a game on device
* B. Another example use case is an application discovering printers on the network.
*
* <p> The API currently supports DNS based service discovery and discovery is currently
* limited to a local network over Multicast DNS. In future, it will be extended to
* support wide area discovery and other service discovery mechanisms.
* DNS service discovery is described at http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
*
* <p> The API is asynchronous and responses to requests from an application are on listener
* callbacks provided by the application. The application needs to do an initialization with
* {@link #initialize} before doing any operation.
* callbacks provided by the application. The application must invoke {@link #initialize} before
* doing any other operation.
*
* <p> Android currently supports DNS based service discovery and it is limited to a local
* network with the use of multicast DNS. In future, this class will be
* extended to support other service discovery mechanisms.
* <p> There are three main operations the API supports - registration, discovery and resolution.
* <pre>
* Application start
* |
* | <----------------------------------------------
* initialize() |
* | |
* | Wait until channel connects |
* | before doing any operation |
* | |
* onChannelConnected() __________ |
* | | |
* | | |
* | onServiceRegistered() | |
* Register any local services / | |
* to be advertised with \ | | If application needs to
* registerService() onFailure() | | do any further operations
* | | | again, it needs to
* | | | initialize() connection
* discoverServices() | | to framework again
* | | |
* Maintain a list to track | |
* discovered services | |
* | | |
* |---------> |-> onChannelDisconnected()
* | | |
* | onServiceFound() |
* | | |
* | add service to list |
* | | |
* |<---------- |
* | |
* |---------> |
* | | |
* | onServiceLost() |
* | | |
* | remove service from list |
* | | |
* |<---------- |
* | |
* | |
* | Connect to a service |
* | from list ? |
* | |
* resolveService() |
* | |
* onServiceResolved() |
* | |
* Establish connection to service |
* with the host and port information |
* | |
* | ___________|
* deinitialize()
* when done with all operations
* or before quit
*
* </pre>
* An application that needs to advertise itself over a network for other applications to
* discover it can do so with a call to {@link #registerService}. If Example is a http based
* application that can provide HTML data to peer services, it can register a name "Example"
* with service type "_http._tcp". A successful registration is notified with a callback to
* {@link DnsSdRegisterListener#onServiceRegistered} and a failure to register is notified
* over {@link DnsSdRegisterListener#onFailure}
*
* <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
* with a call to {@link #discoverServices}. A service found is notified with a callback
* to {@link DnsSdDiscoveryListener#onServiceFound} and a service lost is notified on
* {@link DnsSdDiscoveryListener#onServiceLost}.
*
* <p> Once the peer application discovers the "Example" http srevice, and needs to receive data
* from the "Example" application, it can initiate a resolve with {@link #resolveService} to
* resolve the host and port details for the purpose of establishing a connection. A successful
* resolve is notified on {@link DnsSdResolveListener#onServiceResolved} and a failure is notified
* on {@link DnsSdResolveListener#onFailure}.
*
* Applications can reserve for a service type at
* http://www.iana.org/form/ports-service. Existing services can be found at
* http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
*
* Get an instance of this class by calling {@link android.content.Context#getSystemService(String)
* Context.getSystemService(Context.NSD_SERVICE)}.
* @hide
*
* {@see DnsSdServiceInfo}
*/
public class NsdManager {
private static final String TAG = "NsdManager";
@@ -80,27 +161,32 @@ public class NsdManager {
public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11;
/** @hide */
public static final int UPDATE_SERVICE = BASE + 12;
public static final int UNREGISTER_SERVICE = BASE + 12;
/** @hide */
public static final int UPDATE_SERVICE_FAILED = BASE + 13;
public static final int UNREGISTER_SERVICE_FAILED = BASE + 13;
/** @hide */
public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 14;
public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14;
/** @hide */
public static final int RESOLVE_SERVICE = BASE + 15;
public static final int UPDATE_SERVICE = BASE + 15;
/** @hide */
public static final int RESOLVE_SERVICE_FAILED = BASE + 16;
public static final int UPDATE_SERVICE_FAILED = BASE + 16;
/** @hide */
public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17;
public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 17;
/** @hide */
public static final int STOP_RESOLVE = BASE + 18;
public static final int RESOLVE_SERVICE = BASE + 18;
/** @hide */
public static final int STOP_RESOLVE_FAILED = BASE + 19;
public static final int RESOLVE_SERVICE_FAILED = BASE + 19;
/** @hide */
public static final int STOP_RESOLVE_SUCCEEDED = BASE + 20;
public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20;
/** @hide */
public static final int STOP_RESOLVE = BASE + 21;
/** @hide */
public static final int STOP_RESOLVE_FAILED = BASE + 22;
/** @hide */
public static final int STOP_RESOLVE_SUCCEEDED = BASE + 23;
/**
* Create a new Nsd instance. Applications use
@@ -115,36 +201,44 @@ public class NsdManager {
}
/**
* Passed with onFailure() calls.
* Indicates that the operation failed due to an internal error.
*/
public static final int ERROR = 0;
/**
* Indicates that the operation failed because service discovery is unsupported on the device.
* Passed with onFailure() calls.
* Indicates that the operation failed because service discovery
* is unsupported on the device.
*/
public static final int UNSUPPORTED = 1;
/**
* Indicates that the operation failed because the framework is busy and
* unable to service the request.
* Passed with onFailure() calls.
* Indicates that the operation failed because the framework is
* busy and unable to service the request.
*/
public static final int BUSY = 2;
/**
* Passed with onFailure() calls.
* Indicates that the operation failed because it is already active.
*/
public static final int ALREADY_ACTIVE = 3;
/**
* Passed with onFailure() calls.
* Indicates that the operation failed because maximum limit on
* service registrations has reached.
*/
public static final int MAX_REGS_REACHED = 4;
/** Interface for callback invocation when framework channel is connected or lost */
public interface ChannelListener {
/**
* The channel to the framework is connected.
* Application can initiate calls into the framework using the channel instance passed.
*/
public void onChannelConnected(Channel c);
/**
* The channel to the framework has been disconnected.
@@ -153,6 +247,7 @@ public class NsdManager {
public void onChannelDisconnected();
}
/** Generic interface for callback invocation for a success or failure */
public interface ActionListener {
public void onFailure(int errorCode);
@@ -160,11 +255,12 @@ public class NsdManager {
public void onSuccess();
}
/** Interface for callback invocation for service discovery */
public interface DnsSdDiscoveryListener {
public void onFailure(int errorCode);
public void onStarted(String registrationType);
public void onStarted(String serviceType);
public void onServiceFound(DnsSdServiceInfo serviceInfo);
@@ -172,6 +268,7 @@ public class NsdManager {
}
/** Interface for callback invocation for service registration */
public interface DnsSdRegisterListener {
public void onFailure(int errorCode);
@@ -179,6 +276,7 @@ public class NsdManager {
public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo);
}
/** @hide */
public interface DnsSdUpdateRegistrationListener {
public void onFailure(int errorCode);
@@ -186,6 +284,7 @@ public class NsdManager {
public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord);
}
/** Interface for callback invocation for service resolution */
public interface DnsSdResolveListener {
public void onFailure(int errorCode);
@@ -208,6 +307,7 @@ public class NsdManager {
private DnsSdDiscoveryListener mDnsSdDiscoveryListener;
private ActionListener mDnsSdStopDiscoveryListener;
private DnsSdRegisterListener mDnsSdRegisterListener;
private ActionListener mDnsSdUnregisterListener;
private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
private DnsSdResolveListener mDnsSdResolveListener;
private ActionListener mDnsSdStopResolveListener;
@@ -279,7 +379,17 @@ public class NsdManager {
(DnsSdServiceInfo) message.obj);
}
break;
case UPDATE_SERVICE_FAILED:
case UNREGISTER_SERVICE_FAILED:
if (mDnsSdUnregisterListener != null) {
mDnsSdUnregisterListener.onFailure(message.arg1);
}
break;
case UNREGISTER_SERVICE_SUCCEEDED:
if (mDnsSdUnregisterListener != null) {
mDnsSdUnregisterListener.onSuccess();
}
break;
case UPDATE_SERVICE_FAILED:
if (mDnsSdUpdateListener != null) {
mDnsSdUpdateListener.onFailure(message.arg1);
}
@@ -319,6 +429,10 @@ public class NsdManager {
}
}
private static void checkChannel(Channel c) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
}
/**
* Registers the application with the service discovery framework. This function
* must be the first to be called before any other operations are performed. No service
@@ -327,7 +441,7 @@ public class NsdManager {
*
* @param srcContext is the context of the source
* @param srcLooper is the Looper on which the callbacks are receivied
* @param listener for callback at loss of framework communication.
* @param listener for callback at loss of framework communication. Cannot be null.
*/
public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
Messenger messenger = getMessenger();
@@ -339,88 +453,142 @@ public class NsdManager {
}
/**
* Set the listener for service discovery. Can be null.
* Disconnects application from service discovery framework. No further operations
* will succeed until a {@link #initialize} is called again.
*
* @param c channel initialized with {@link #initialize}
*/
public void setDiscoveryListener(Channel c, DnsSdDiscoveryListener b) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdDiscoveryListener = b;
public void deinitialize(Channel c) {
checkChannel(c);
c.mAsyncChannel.disconnect();
}
/**
* Set the listener for stop service discovery. Can be null.
* Register a service to be discovered by other services.
*
* <p> The function call immediately returns after sending a request to register service
* to the framework. The application is notified of a success to initiate
* discovery through the callback {@link DnsSdRegisterListener#onServiceRegistered} or a failure
* through {@link DnsSdRegisterListener#onFailure}.
*
* @param c is the channel created at {@link #initialize}
* @param serviceType The service type being advertised.
* @param port on which the service is listenering for incoming connections
* @param listener for success or failure callback. Can be null.
*/
public void setStopDiscoveryListener(Channel c, ActionListener a) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdStopDiscoveryListener = a;
}
/**
* Set the listener for service registration. Can be null.
*/
public void setRegisterListener(Channel c, DnsSdRegisterListener b) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdRegisterListener = b;
}
/**
* Set the listener for service registration. Can be null.
*/
public void setUpdateRegistrationListener(Channel c, DnsSdUpdateRegistrationListener b) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdUpdateListener = b;
}
/**
* Set the listener for service resolution. Can be null.
*/
public void setResolveListener(Channel c, DnsSdResolveListener b) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdResolveListener = b;
}
/**
* Set the listener for stopping service resolution. Can be null.
*/
public void setStopResolveListener(Channel c, ActionListener b) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
c.mDnsSdStopResolveListener = b;
}
public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
public void registerService(Channel c, String serviceName, String serviceType, int port,
DnsSdRegisterListener listener) {
checkChannel(c);
if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
throw new IllegalArgumentException("Service name or type cannot be empty");
}
if (port <= 0) {
throw new IllegalArgumentException("Invalid port number");
}
DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null);
serviceInfo.setPort(port);
c.mDnsSdRegisterListener = listener;
c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo);
}
/**
* Unregister a service registered through {@link #registerService}
* @param c is the channel created at {@link #initialize}
* @param registeredId is obtained at {@link DnsSdRegisterListener#onServiceRegistered}
* @param listener provides callbacks for success or failure. Can be null.
*/
public void unregisterService(Channel c, int registeredId, ActionListener listener) {
checkChannel(c);
c.mDnsSdUnregisterListener = listener;
c.mAsyncChannel.sendMessage(UNREGISTER_SERVICE, registeredId);
}
/** @hide */
public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
checkChannel(c);
c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord);
}
public void discoverServices(Channel c, String serviceType) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (c.mDnsSdDiscoveryListener == null) throw new
IllegalStateException("Discovery listener needs to be set first");
/**
* Initiate service discovery to browse for instances of a service type. Service discovery
* consumes network bandwidth and will continue until the application calls
* {@link #stopServiceDiscovery}.
*
* <p> The function call immediately returns after sending a request to start service
* discovery to the framework. The application is notified of a success to initiate
* discovery through the callback {@link DnsSdDiscoveryListener#onStarted} or a failure
* through {@link DnsSdDiscoveryListener#onFailure}.
*
* <p> Upon successful start, application is notified when a service is found with
* {@link DnsSdDiscoveryListener#onServiceFound} or when a service is lost with
* {@link DnsSdDiscoveryListener#onServiceLost}.
*
* <p> Upon failure to start, service discovery is not active and application does
* not need to invoke {@link #stopServiceDiscovery}
*
* @param c is the channel created at {@link #initialize}
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
* http services or "_ipp._tcp" for printers
* @param listener provides callbacks when service is found or lost. Cannot be null.
*/
public void discoverServices(Channel c, String serviceType, DnsSdDiscoveryListener listener) {
checkChannel(c);
if (listener == null) {
throw new IllegalStateException("Discovery listener needs to be set first");
}
if (TextUtils.isEmpty(serviceType)) {
throw new IllegalStateException("Service type cannot be empty");
}
DnsSdServiceInfo s = new DnsSdServiceInfo();
s.setServiceType(serviceType);
c.mDnsSdDiscoveryListener = listener;
c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s);
}
public void stopServiceDiscovery(Channel c) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
/**
* Stop service discovery initiated with {@link #discoverServices}. An active service
* discovery is notified to the application with {@link DnsSdDiscoveryListener#onStarted}
* and it stays active until the application invokes a stop service discovery.
*
* <p> Upon failure to start service discovery notified through
* {@link DnsSdDiscoveryListener#onFailure} service discovery is not active and
* application does not need to stop it.
*
* @param c is the channel created at {@link #initialize}
* @param listener notifies success or failure. Can be null.
*/
public void stopServiceDiscovery(Channel c, ActionListener listener) {
checkChannel(c);
c.mDnsSdStopDiscoveryListener = listener;
c.mAsyncChannel.sendMessage(STOP_DISCOVERY);
}
public void resolveService(Channel c, DnsSdServiceInfo serviceInfo) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
if (c.mDnsSdResolveListener == null) throw new
IllegalStateException("Resolve listener needs to be set first");
/**
* Resolve a discovered service. An application can resolve a service right before
* establishing a connection to fetch the IP and port details on which to setup
* the connection.
*
* @param c is the channel created at {@link #initialize}
* @param serviceName of the the service
* @param serviceType of the service
* @param listener to receive callback upon success or failure. Cannot be null.
*/
public void resolveService(Channel c, String serviceName, String serviceType,
DnsSdResolveListener listener) {
checkChannel(c);
if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
throw new IllegalArgumentException("Service name or type cannot be empty");
}
if (listener == null) throw new
IllegalStateException("Resolve listener cannot be null");
c.mDnsSdResolveListener = listener;
DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null);
c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
}
/** @hide */
public void stopServiceResolve(Channel c) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
checkChannel(c);
if (c.mDnsSdResolveListener == null) throw new
IllegalStateException("Resolve listener needs to be set first");
c.mAsyncChannel.sendMessage(STOP_RESOLVE);

View File

@@ -167,6 +167,18 @@ public class NsdService extends INsdManager.Stub {
NsdManager.ERROR);
}
break;
case NsdManager.UNREGISTER_SERVICE:
if (DBG) Slog.d(TAG, "unregister service");
clientInfo = mClients.get(msg.replyTo);
int regId = msg.arg1;
if (clientInfo.mRegisteredIds.remove(new Integer(regId)) &&
unregisterService(regId)) {
mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
} else {
mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
NsdManager.ERROR);
}
break;
case NsdManager.UPDATE_SERVICE:
if (DBG) Slog.d(TAG, "Update service");
//TODO: implement
@@ -237,6 +249,8 @@ public class NsdService extends INsdManager.Stub {
}
public Messenger getMessenger() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
"NsdService");
return new Messenger(mAsyncServiceHandler);
}