diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 77e97e1063..08ba728f65 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -22,12 +22,16 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Handler; +import android.os.HandlerThread; 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 android.util.SparseArray; + +import java.util.concurrent.CountDownLatch; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; @@ -39,88 +43,73 @@ import com.android.internal.util.Protocol; * B. Another example use case is an application discovering printers on the network. * *

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 + * limited to a local network over Multicast DNS. DNS service discovery is described at + * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt * *

The API is asynchronous and responses to requests from an application are on listener - * callbacks provided by the application. The application must invoke {@link #initialize} before - * doing any other operation. + * callbacks on a seperate thread. * *

There are three main operations the API supports - registration, discovery and resolution. *

  *                          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
+ *                                 |
+ *                                 |                  onServiceRegistered()
+ *                     Register any local services  /
+ *                      to be advertised with       \
+ *                       registerService()            onRegistrationFailed()
+ *                                 |
+ *                                 |
+ *                          discoverServices()
+ *                                 |
+ *                      Maintain a list to track
+ *                        discovered services
+ *                                 |
+ *                                 |--------->
+ *                                 |          |
+ *                                 |      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
  *
  * 
* 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} + * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified + * over {@link RegistrationListener#onRegistrationFailed} * *

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}. + * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on + * {@link DiscoveryListener#onServiceLost}. * *

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}. + * resolve is notified on {@link ResolveListener#onServiceResolved} and a failure is notified + * on {@link ResolveListener#onResolveFailed}. * * Applications can reserve for a service type at * http://www.iana.org/form/ports-service. Existing services can be found at @@ -129,9 +118,9 @@ import com.android.internal.util.Protocol; * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) * Context.getSystemService(Context.NSD_SERVICE)}. * - * {@see DnsSdServiceInfo} + * {@see NsdServiceInfo} */ -public class NsdManager { +public final class NsdManager { private static final String TAG = "NsdManager"; INsdManager mService; @@ -203,13 +192,6 @@ public class NsdManager { /** @hide */ public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14; - /** @hide */ - public static final int UPDATE_SERVICE = BASE + 15; - /** @hide */ - public static final int UPDATE_SERVICE_FAILED = BASE + 16; - /** @hide */ - public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 17; - /** @hide */ public static final int RESOLVE_SERVICE = BASE + 18; /** @hide */ @@ -217,18 +199,28 @@ public class NsdManager { /** @hide */ 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; - /** @hide */ public static final int ENABLE = BASE + 24; /** @hide */ public static final int DISABLE = BASE + 25; + /** @hide */ + public static final int NATIVE_DAEMON_EVENT = BASE + 26; + + /** Dns based service discovery protocol */ + public static final int PROTOCOL_DNS_SD = 0x0001; + + private Context mContext; + + private static final int INVALID_LISTENER_KEY = 0; + private int mListenerKey = 1; + private final SparseArray mListenerMap = new SparseArray(); + private final SparseArray mServiceMap = new SparseArray(); + private final Object mMapLock = new Object(); + + private final AsyncChannel mAsyncChannel = new AsyncChannel(); + private ServiceHandler mHandler; + private final CountDownLatch mConnected = new CountDownLatch(1); /** * Create a new Nsd instance. Applications use @@ -238,271 +230,213 @@ public class NsdManager { * @hide - hide this because it takes in a parameter of type INsdManager, which * is a system private class. */ - public NsdManager(INsdManager service) { + public NsdManager(Context context, INsdManager service) { mService = service; + mContext = context; + init(); } /** - * Passed with onFailure() calls. + * Failures are passed with {@link RegistrationListener#onRegistrationFailed}, + * {@link RegistrationListener#onUnregistrationFailed}, + * {@link DiscoveryListener#onStartDiscoveryFailed}, + * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}. + * * Indicates that the operation failed due to an internal error. */ - public static final int ERROR = 0; + public static final int FAILURE_INTERNAL_ERROR = 0; /** - * Passed with onFailure() calls. - * Indicates that the operation failed because service discovery - * is unsupported on the device. - */ - public static final int UNSUPPORTED = 1; - - /** - * 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; + public static final int FAILURE_ALREADY_ACTIVE = 3; /** - * Passed with onFailure() calls. - * Indicates that the operation failed because maximum limit on - * service registrations has reached. + * Indicates that the operation failed because the maximum outstanding + * requests from the applications have 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. - * Application could try re-initializing using {@link #initialize} - */ - public void onChannelDisconnected(); - } - - /** Generic interface for callback invocation for a success or failure */ - public interface ActionListener { - - public void onFailure(int errorCode); - - public void onSuccess(); - } + public static final int FAILURE_MAX_LIMIT = 4; /** Interface for callback invocation for service discovery */ - public interface DnsSdDiscoveryListener { + public interface DiscoveryListener { - public void onFailure(int errorCode); + public void onStartDiscoveryFailed(String serviceType, int errorCode); - public void onStarted(String serviceType); + public void onStopDiscoveryFailed(String serviceType, int errorCode); - public void onServiceFound(DnsSdServiceInfo serviceInfo); + public void onDiscoveryStarted(String serviceType); - public void onServiceLost(DnsSdServiceInfo serviceInfo); + public void onDiscoveryStopped(String serviceType); + + public void onServiceFound(NsdServiceInfo serviceInfo); + + public void onServiceLost(NsdServiceInfo serviceInfo); } /** Interface for callback invocation for service registration */ - public interface DnsSdRegisterListener { + public interface RegistrationListener { - public void onFailure(int errorCode); + public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode); - public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); - } + public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode); - /** @hide */ - public interface DnsSdUpdateRegistrationListener { + public void onServiceRegistered(NsdServiceInfo serviceInfo); - public void onFailure(int errorCode); - - public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); + public void onServiceUnregistered(NsdServiceInfo serviceInfo); } /** Interface for callback invocation for service resolution */ - public interface DnsSdResolveListener { + public interface ResolveListener { - public void onFailure(int errorCode); + public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode); - public void onServiceResolved(DnsSdServiceInfo serviceInfo); + public void onServiceResolved(NsdServiceInfo serviceInfo); } - /** - * A channel that connects the application to the NetworkService framework. - * Most service operations require a Channel as an argument. An instance of Channel is obtained - * by doing a call on {@link #initialize} - */ - public static class Channel { - Channel(Looper looper, ChannelListener l) { - mAsyncChannel = new AsyncChannel(); - mHandler = new ServiceHandler(looper); - mChannelListener = l; + private class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); } - private ChannelListener mChannelListener; - private DnsSdDiscoveryListener mDnsSdDiscoveryListener; - private ActionListener mDnsSdStopDiscoveryListener; - private DnsSdRegisterListener mDnsSdRegisterListener; - private ActionListener mDnsSdUnregisterListener; - private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; - private DnsSdResolveListener mDnsSdResolveListener; - private ActionListener mDnsSdStopResolveListener; - private AsyncChannel mAsyncChannel; - private ServiceHandler mHandler; - class ServiceHandler extends Handler { - ServiceHandler(Looper looper) { - super(looper); + @Override + public void handleMessage(Message message) { + Object listener = getListener(message.arg2); + boolean listenerRemove = true; + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + mConnected.countDown(); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + // Ignore + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "Channel lost"); + break; + case DISCOVER_SERVICES_STARTED: + String s = ((NsdServiceInfo) message.obj).getServiceType(); + ((DiscoveryListener) listener).onDiscoveryStarted(s); + // Keep listener until stop discovery + listenerRemove = false; + break; + case DISCOVER_SERVICES_FAILED: + ((DiscoveryListener) listener).onStartDiscoveryFailed( + getNsdService(message.arg2).getServiceType(), message.arg1); + break; + case SERVICE_FOUND: + ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj); + // Keep listener until stop discovery + listenerRemove = false; + break; + case SERVICE_LOST: + ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj); + // Keep listener until stop discovery + listenerRemove = false; + break; + case STOP_DISCOVERY_FAILED: + ((DiscoveryListener) listener).onStopDiscoveryFailed( + getNsdService(message.arg2).getServiceType(), message.arg1); + break; + case STOP_DISCOVERY_SUCCEEDED: + ((DiscoveryListener) listener).onDiscoveryStopped( + getNsdService(message.arg2).getServiceType()); + break; + case REGISTER_SERVICE_FAILED: + ((RegistrationListener) listener).onRegistrationFailed( + getNsdService(message.arg2), message.arg1); + break; + case REGISTER_SERVICE_SUCCEEDED: + ((RegistrationListener) listener).onServiceRegistered( + (NsdServiceInfo) message.obj); + // Keep listener until unregister + listenerRemove = false; + break; + case UNREGISTER_SERVICE_FAILED: + ((RegistrationListener) listener).onUnregistrationFailed( + getNsdService(message.arg2), message.arg1); + break; + case UNREGISTER_SERVICE_SUCCEEDED: + ((RegistrationListener) listener).onServiceUnregistered( + getNsdService(message.arg2)); + break; + case RESOLVE_SERVICE_FAILED: + ((ResolveListener) listener).onResolveFailed( + getNsdService(message.arg2), message.arg1); + break; + case RESOLVE_SERVICE_SUCCEEDED: + ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj); + break; + default: + Log.d(TAG, "Ignored " + message); + break; } - - @Override - public void handleMessage(Message message) { - switch (message.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - break; - case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: - if (mChannelListener != null) { - mChannelListener.onChannelConnected(Channel.this); - } - break; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - if (mChannelListener != null) { - mChannelListener.onChannelDisconnected(); - mChannelListener = null; - } - break; - case DISCOVER_SERVICES_STARTED: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onStarted((String) message.obj); - } - break; - case DISCOVER_SERVICES_FAILED: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onFailure(message.arg1); - } - break; - case SERVICE_FOUND: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onServiceFound( - (DnsSdServiceInfo) message.obj); - } - break; - case SERVICE_LOST: - if (mDnsSdDiscoveryListener != null) { - mDnsSdDiscoveryListener.onServiceLost( - (DnsSdServiceInfo) message.obj); - } - break; - case STOP_DISCOVERY_FAILED: - if (mDnsSdStopDiscoveryListener != null) { - mDnsSdStopDiscoveryListener.onFailure(message.arg1); - } - break; - case STOP_DISCOVERY_SUCCEEDED: - if (mDnsSdStopDiscoveryListener != null) { - mDnsSdStopDiscoveryListener.onSuccess(); - } - break; - case REGISTER_SERVICE_FAILED: - if (mDnsSdRegisterListener != null) { - mDnsSdRegisterListener.onFailure(message.arg1); - } - break; - case REGISTER_SERVICE_SUCCEEDED: - if (mDnsSdRegisterListener != null) { - mDnsSdRegisterListener.onServiceRegistered(message.arg1, - (DnsSdServiceInfo) message.obj); - } - break; - 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); - } - break; - case UPDATE_SERVICE_SUCCEEDED: - if (mDnsSdUpdateListener != null) { - mDnsSdUpdateListener.onServiceUpdated(message.arg1, - (DnsSdTxtRecord) message.obj); - } - break; - case RESOLVE_SERVICE_FAILED: - if (mDnsSdResolveListener != null) { - mDnsSdResolveListener.onFailure(message.arg1); - } - break; - case RESOLVE_SERVICE_SUCCEEDED: - if (mDnsSdResolveListener != null) { - mDnsSdResolveListener.onServiceResolved( - (DnsSdServiceInfo) message.obj); - } - break; - case STOP_RESOLVE_FAILED: - if (mDnsSdStopResolveListener!= null) { - mDnsSdStopResolveListener.onFailure(message.arg1); - } - break; - case STOP_RESOLVE_SUCCEEDED: - if (mDnsSdStopResolveListener != null) { - mDnsSdStopResolveListener.onSuccess(); - } - break; - default: - Log.d(TAG, "Ignored " + message); - break; - } + if (listenerRemove) { + removeListener(message.arg2); } } - } - - private static void checkChannel(Channel c) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); } + private int putListener(Object listener, NsdServiceInfo s) { + if (listener == null) return INVALID_LISTENER_KEY; + int key; + synchronized (mMapLock) { + do { + key = mListenerKey++; + } while (key == INVALID_LISTENER_KEY); + mListenerMap.put(key, listener); + mServiceMap.put(key, s); + } + return key; + } + + private Object getListener(int key) { + if (key == INVALID_LISTENER_KEY) return null; + synchronized (mMapLock) { + return mListenerMap.get(key); + } + } + + private NsdServiceInfo getNsdService(int key) { + synchronized (mMapLock) { + return mServiceMap.get(key); + } + } + + private void removeListener(int key) { + if (key == INVALID_LISTENER_KEY) return; + synchronized (mMapLock) { + mListenerMap.remove(key); + mServiceMap.remove(key); + } + } + + private int getListenerKey(Object listener) { + synchronized (mMapLock) { + int valueIndex = mListenerMap.indexOfValue(listener); + if (valueIndex != -1) { + return mListenerMap.keyAt(valueIndex); + } + } + return INVALID_LISTENER_KEY; + } + + /** - * 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 - * discovery operations must be performed until the ChannelListener callback notifies - * that the channel is connected - * - * @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. Cannot be null. + * Initialize AsyncChannel */ - public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { - Messenger messenger = getMessenger(); + private void init() { + final Messenger messenger = getMessenger(); if (messenger == null) throw new RuntimeException("Failed to initialize"); - if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); - - Channel c = new Channel(srcLooper, listener); - c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); - } - - /** - * 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 deinitialize(Channel c) { - checkChannel(c); - c.mAsyncChannel.disconnect(); + HandlerThread t = new HandlerThread("NsdManager"); + t.start(); + mHandler = new ServiceHandler(t.getLooper()); + mAsyncChannel.connect(mContext, mHandler, messenger); + try { + mConnected.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted wait at init"); + } } /** @@ -510,45 +444,51 @@ public class NsdManager { * *

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}. + * discovery through the callback {@link RegistrationListener#onServiceRegistered} or a failure + * through {@link RegistrationListener#onRegistrationFailed}. * - * @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. + * @param serviceInfo The service being registered + * @param protocolType The service discovery protocol + * @param listener The listener notifies of a successful registration and is used to + * unregister this service through a call on {@link #unregisterService}. Cannot be null. */ - public void registerService(Channel c, String serviceName, String serviceType, int port, - DnsSdRegisterListener listener) { - checkChannel(c); - if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) { + public void registerService(NsdServiceInfo serviceInfo, int protocolType, + RegistrationListener listener) { + if (TextUtils.isEmpty(serviceInfo.getServiceName()) || + TextUtils.isEmpty(serviceInfo.getServiceType())) { throw new IllegalArgumentException("Service name or type cannot be empty"); } - if (port <= 0) { + if (serviceInfo.getPort() <= 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); + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + if (protocolType != PROTOCOL_DNS_SD) { + throw new IllegalArgumentException("Unsupported protocol"); + } + mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, putListener(listener, serviceInfo), + 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. + * Unregister a service registered through {@link #registerService}. A successful + * unregister is notified to the application with a call to + * {@link RegistrationListener#onServiceUnregistered}. + * + * @param listener This should be the listener object that was passed to + * {@link #registerService}. It identifies the service that should be unregistered + * and notifies of a successful unregistration. */ - 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) { - checkChannel(c); - c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); + public void unregisterService(RegistrationListener listener) { + int id = getListenerKey(listener); + if (id == INVALID_LISTENER_KEY) { + throw new IllegalArgumentException("listener not registered"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id); } /** @@ -558,51 +498,61 @@ public class NsdManager { * *

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}. + * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure + * through {@link DiscoveryListener#onStartDiscoveryFailed}. * *

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}. + * {@link DiscoveryListener#onServiceFound} or when a service is lost with + * {@link DiscoveryListener#onServiceLost}. * *

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. + * @param protocolType The service discovery protocol + * @param listener The listener notifies of a successful discovery and is used + * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. + * Cannot be null. */ - public void discoverServices(Channel c, String serviceType, DnsSdDiscoveryListener listener) { - checkChannel(c); + public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { if (listener == null) { - throw new IllegalStateException("Discovery listener needs to be set first"); + throw new IllegalArgumentException("listener cannot be null"); } if (TextUtils.isEmpty(serviceType)) { - throw new IllegalStateException("Service type cannot be empty"); + throw new IllegalArgumentException("Service type cannot be empty"); } - DnsSdServiceInfo s = new DnsSdServiceInfo(); + + if (protocolType != PROTOCOL_DNS_SD) { + throw new IllegalArgumentException("Unsupported protocol"); + } + + NsdServiceInfo s = new NsdServiceInfo(); s.setServiceType(serviceType); - c.mDnsSdDiscoveryListener = listener; - c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); + mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, putListener(listener, s), s); } /** * 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. + * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted} + * and it stays active until the application invokes a stop service discovery. A successful + * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}. * - *

Upon failure to start service discovery notified through - * {@link DnsSdDiscoveryListener#onFailure} service discovery is not active and - * application does not need to stop it. + *

Upon failure to stop service discovery, application is notified through + * {@link DiscoveryListener#onStopDiscoveryFailed}. * - * @param c is the channel created at {@link #initialize} - * @param listener notifies success or failure. Can be null. + * @param listener This should be the listener object that was passed to {@link #discoverServices}. + * It identifies the discovery that should be stopped and notifies of a successful stop. */ - public void stopServiceDiscovery(Channel c, ActionListener listener) { - checkChannel(c); - c.mDnsSdStopDiscoveryListener = listener; - c.mAsyncChannel.sendMessage(STOP_DISCOVERY); + public void stopServiceDiscovery(DiscoveryListener listener) { + int id = getListenerKey(listener); + if (id == INVALID_LISTENER_KEY) { + throw new IllegalArgumentException("service discovery not active on listener"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id); } /** @@ -610,30 +560,19 @@ public class NsdManager { * 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 serviceInfo service to be resolved * @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)) { + public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) { + if (TextUtils.isEmpty(serviceInfo.getServiceName()) || + TextUtils.isEmpty(serviceInfo.getServiceType())) { 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) { - checkChannel(c); - if (c.mDnsSdResolveListener == null) throw new - IllegalStateException("Resolve listener needs to be set first"); - c.mAsyncChannel.sendMessage(STOP_RESOLVE); + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, putListener(listener, serviceInfo), + serviceInfo); } /** Internal use only @hide */ diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java similarity index 86% rename from core/java/android/net/nsd/DnsSdServiceInfo.java rename to core/java/android/net/nsd/NsdServiceInfo.java index 66abd3a321..205a21d95b 100644 --- a/core/java/android/net/nsd/DnsSdServiceInfo.java +++ b/core/java/android/net/nsd/NsdServiceInfo.java @@ -25,7 +25,7 @@ import java.net.InetAddress; * A class representing service information for network service discovery * {@see NsdManager} */ -public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { +public final class NsdServiceInfo implements Parcelable { private String mServiceName; @@ -37,36 +37,32 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { private int mPort; - public DnsSdServiceInfo() { + public NsdServiceInfo() { } /** @hide */ - public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { + public NsdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { mServiceName = sn; mServiceType = rt; mTxtRecord = tr; } /** Get the service name */ - @Override public String getServiceName() { return mServiceName; } /** Set the service name */ - @Override public void setServiceName(String s) { mServiceName = s; } /** Get the service type */ - @Override public String getServiceType() { return mServiceType; } /** Set the service type */ - @Override public void setServiceType(String s) { mServiceType = s; } @@ -132,10 +128,10 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { } /** Implement the Parcelable interface */ - public static final Creator CREATOR = - new Creator() { - public DnsSdServiceInfo createFromParcel(Parcel in) { - DnsSdServiceInfo info = new DnsSdServiceInfo(); + public static final Creator CREATOR = + new Creator() { + public NsdServiceInfo createFromParcel(Parcel in) { + NsdServiceInfo info = new NsdServiceInfo(); info.mServiceName = in.readString(); info.mServiceType = in.readString(); info.mTxtRecord = in.readParcelable(null); @@ -150,8 +146,8 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { return info; } - public DnsSdServiceInfo[] newArray(int size) { - return new DnsSdServiceInfo[size]; + public NsdServiceInfo[] newArray(int size) { + return new NsdServiceInfo[size]; } }; } diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java index f33bf8b24e..cc8e6a4b2d 100644 --- a/services/java/com/android/server/NsdService.java +++ b/services/java/com/android/server/NsdService.java @@ -20,7 +20,7 @@ import android.content.Context; import android.content.ContentResolver; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.nsd.DnsSdServiceInfo; +import android.net.nsd.NsdServiceInfo; import android.net.nsd.DnsSdTxtRecord; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; @@ -32,6 +32,7 @@ import android.os.Messenger; import android.os.IBinder; import android.provider.Settings; import android.util.Slog; +import android.util.SparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -72,13 +73,16 @@ public class NsdService extends INsdManager.Stub { */ private HashMap mClients = new HashMap(); + /* A map from unique id to client info */ + private SparseArray mIdToClientInfoMap= new SparseArray(); + private AsyncChannel mReplyChannel = new AsyncChannel(); private int INVALID_ID = 0; private int mUniqueId = 1; private static final int BASE = Protocol.BASE_NSD_MANAGER; - private static final int CMD_TO_STRING_COUNT = NsdManager.STOP_RESOLVE - BASE + 1; + private static final int CMD_TO_STRING_COUNT = NsdManager.RESOLVE_SERVICE - BASE + 1; private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT]; static { @@ -87,7 +91,6 @@ public class NsdService extends INsdManager.Stub { sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER"; sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER"; sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE"; - sCmdToString[NsdManager.STOP_RESOLVE - BASE] = "STOP-RESOLVE"; } private static String cmdToString(int cmd) { @@ -101,9 +104,9 @@ public class NsdService extends INsdManager.Stub { private class NsdStateMachine extends StateMachine { - private DefaultState mDefaultState = new DefaultState(); - private DisabledState mDisabledState = new DisabledState(); - private EnabledState mEnabledState = new EnabledState(); + private final DefaultState mDefaultState = new DefaultState(); + private final DisabledState mDisabledState = new DisabledState(); + private final EnabledState mEnabledState = new EnabledState(); @Override protected String getMessageInfo(Message msg) { @@ -151,29 +154,26 @@ public class NsdService extends INsdManager.Stub { ac.connect(mContext, getHandler(), msg.replyTo); break; case NsdManager.DISCOVER_SERVICES: - mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.BUSY); + replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; case NsdManager.STOP_DISCOVERY: - mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; case NsdManager.REGISTER_SERVICE: - mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; case NsdManager.UNREGISTER_SERVICE: - mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; case NsdManager.RESOLVE_SERVICE: - mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.ERROR); - break; - case NsdManager.STOP_RESOLVE: - mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; + case NsdManager.NATIVE_DAEMON_EVENT: default: Slog.e(TAG, "Unhandled " + msg); return NOT_HANDLED; @@ -217,11 +217,30 @@ public class NsdService extends INsdManager.Stub { } } + private boolean requestLimitReached(ClientInfo clientInfo) { + if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) { + if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo); + return true; + } + return false; + } + + private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo) { + clientInfo.mClientIds.put(clientId, globalId); + mIdToClientInfoMap.put(globalId, clientInfo); + } + + private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) { + clientInfo.mClientIds.remove(clientId); + mIdToClientInfoMap.remove(globalId); + } + @Override public boolean processMessage(Message msg) { ClientInfo clientInfo; - DnsSdServiceInfo servInfo; + NsdServiceInfo servInfo; boolean result = HANDLED; + int id; switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: //First client @@ -244,111 +263,112 @@ public class NsdService extends INsdManager.Stub { break; case NsdManager.DISCOVER_SERVICES: if (DBG) Slog.d(TAG, "Discover services"); - servInfo = (DnsSdServiceInfo) msg.obj; + servInfo = (NsdServiceInfo) msg.obj; clientInfo = mClients.get(msg.replyTo); - if (clientInfo.mDiscoveryId != INVALID_ID) { - //discovery already in progress - if (DBG) Slog.d(TAG, "discovery in progress"); - mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.ALREADY_ACTIVE); + + if (requestLimitReached(clientInfo)) { + replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.FAILURE_MAX_LIMIT); break; } - clientInfo.mDiscoveryId = getUniqueId(); - if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) { - mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED); + + id = getUniqueId(); + if (discoverServices(id, servInfo.getServiceType())) { + if (DBG) { + Slog.d(TAG, "Discover " + msg.arg2 + " " + id + + servInfo.getServiceType()); + } + storeRequestMap(msg.arg2, id, clientInfo); + replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo); } else { - mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.ERROR); - clientInfo.mDiscoveryId = INVALID_ID; + stopServiceDiscovery(id); + replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.STOP_DISCOVERY: if (DBG) Slog.d(TAG, "Stop service discovery"); clientInfo = mClients.get(msg.replyTo); - if (clientInfo.mDiscoveryId == INVALID_ID) { - //already stopped - if (DBG) Slog.d(TAG, "discovery already stopped"); - mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.ALREADY_ACTIVE); + + try { + id = clientInfo.mClientIds.get(msg.arg2).intValue(); + } catch (NullPointerException e) { + replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); break; } - if (stopServiceDiscovery(clientInfo.mDiscoveryId)) { - clientInfo.mDiscoveryId = INVALID_ID; - mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED); + removeRequestMap(msg.arg2, id, clientInfo); + if (stopServiceDiscovery(id)) { + replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED); } else { - mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.REGISTER_SERVICE: if (DBG) Slog.d(TAG, "Register service"); clientInfo = mClients.get(msg.replyTo); - if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) { - if (DBG) Slog.d(TAG, "register service exceeds limit"); - mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.MAX_REGS_REACHED); + if (requestLimitReached(clientInfo)) { + replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.FAILURE_MAX_LIMIT); + break; } - int id = getUniqueId(); - if (registerService(id, (DnsSdServiceInfo) msg.obj)) { - clientInfo.mRegisteredIds.add(id); + id = getUniqueId(); + if (registerService(id, (NsdServiceInfo) msg.obj)) { + if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id); + storeRequestMap(msg.arg2, id, clientInfo); + // Return success after mDns reports success } else { - mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.ERROR); + unregisterService(id); + replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_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); + try { + id = clientInfo.mClientIds.get(msg.arg2).intValue(); + } catch (NullPointerException e) { + replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); + break; + } + removeRequestMap(msg.arg2, id, clientInfo); + if (unregisterService(id)) { + replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED); + } else { + replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); } - break; - case NsdManager.UPDATE_SERVICE: - if (DBG) Slog.d(TAG, "Update service"); - //TODO: implement - mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED); break; case NsdManager.RESOLVE_SERVICE: if (DBG) Slog.d(TAG, "Resolve service"); - servInfo = (DnsSdServiceInfo) msg.obj; + servInfo = (NsdServiceInfo) msg.obj; clientInfo = mClients.get(msg.replyTo); - if (clientInfo.mResolveId != INVALID_ID) { - //first cancel existing resolve - stopResolveService(clientInfo.mResolveId); - } - clientInfo.mResolveId = getUniqueId(); - if (!resolveService(clientInfo.mResolveId, servInfo)) { - mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.ERROR); - clientInfo.mResolveId = INVALID_ID; - } - break; - case NsdManager.STOP_RESOLVE: - if (DBG) Slog.d(TAG, "Stop resolve"); - clientInfo = mClients.get(msg.replyTo); - if (clientInfo.mResolveId == INVALID_ID) { - //already stopped - if (DBG) Slog.d(TAG, "resolve already stopped"); - mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, - NsdManager.ALREADY_ACTIVE); + + if (clientInfo.mResolvedService != null) { + replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_ALREADY_ACTIVE); break; } - if (stopResolveService(clientInfo.mResolveId)) { - clientInfo.mResolveId = INVALID_ID; - mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED); + + id = getUniqueId(); + if (resolveService(id, servInfo)) { + clientInfo.mResolvedService = new NsdServiceInfo(); + storeRequestMap(msg.arg2, id, clientInfo); } else { - mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, - NsdManager.ERROR); + replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR); } break; + case NsdManager.NATIVE_DAEMON_EVENT: + NativeEvent event = (NativeEvent) msg.obj; + handleNativeEvent(event.code, event.raw, + NativeDaemonEvent.unescapeArgs(event.raw)); + break; default: result = NOT_HANDLED; break; @@ -439,121 +459,144 @@ public class NsdService extends INsdManager.Stub { public static final int SERVICE_GET_ADDR_SUCCESS = 612; } + private class NativeEvent { + int code; + String raw; + + NativeEvent(int code, String raw) { + this.code = code; + this.raw = raw; + } + } + class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks { public void onDaemonConnected() { mNativeDaemonConnected.countDown(); } public boolean onEvent(int code, String raw, String[] cooked) { - ClientInfo clientInfo; - DnsSdServiceInfo servInfo; - int id = Integer.parseInt(cooked[1]); - switch (code) { - case NativeResponseCode.SERVICE_FOUND: - /* NNN uniqueId serviceName regType domain */ - if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw); - clientInfo = getClientByDiscovery(id); - if (clientInfo == null) break; + // TODO: NDC translates a message to a callback, we could enhance NDC to + // directly interact with a state machine through messages + NativeEvent event = new NativeEvent(code, raw); + mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event); + return true; + } + } - servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null); - clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, servInfo); + private void handleNativeEvent(int code, String raw, String[] cooked) { + NsdServiceInfo servInfo; + int id = Integer.parseInt(cooked[1]); + ClientInfo clientInfo = mIdToClientInfoMap.get(id); + if (clientInfo == null) { + Slog.e(TAG, "Unique id with no client mapping: " + id); + return; + } + + /* This goes in response as msg.arg2 */ + int clientId = -1; + int keyId = clientInfo.mClientIds.indexOfValue(id); + if (keyId != -1) { + clientId = clientInfo.mClientIds.keyAt(keyId); + } + switch (code) { + case NativeResponseCode.SERVICE_FOUND: + /* NNN uniqueId serviceName regType domain */ + if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw); + servInfo = new NsdServiceInfo(cooked[2], cooked[3], null); + clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0, + clientId, servInfo); + break; + case NativeResponseCode.SERVICE_LOST: + /* NNN uniqueId serviceName regType domain */ + if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw); + servInfo = new NsdServiceInfo(cooked[2], cooked[3], null); + clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0, + clientId, servInfo); + break; + case NativeResponseCode.SERVICE_DISCOVERY_FAILED: + /* NNN uniqueId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw); + clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + break; + case NativeResponseCode.SERVICE_REGISTERED: + /* NNN regId serviceName regType */ + if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw); + servInfo = new NsdServiceInfo(cooked[2], null, null); + clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED, + id, clientId, servInfo); + break; + case NativeResponseCode.SERVICE_REGISTRATION_FAILED: + /* NNN regId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw); + clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + break; + case NativeResponseCode.SERVICE_UPDATED: + /* NNN regId */ + break; + case NativeResponseCode.SERVICE_UPDATE_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_RESOLVED: + /* NNN resolveId fullName hostName port txtlen txtdata */ + if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw); + int index = cooked[2].indexOf("."); + if (index == -1) { + Slog.e(TAG, "Invalid service found " + raw); break; - case NativeResponseCode.SERVICE_LOST: - /* NNN uniqueId serviceName regType domain */ - if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw); - clientInfo = getClientByDiscovery(id); - if (clientInfo == null) break; + } + String name = cooked[2].substring(0, index); + String rest = cooked[2].substring(index); + String type = rest.replace(".local.", ""); - servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null); - clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, servInfo); - break; - case NativeResponseCode.SERVICE_DISCOVERY_FAILED: - /* NNN uniqueId errorCode */ - if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw); - clientInfo = getClientByDiscovery(id); - if (clientInfo == null) break; - - clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.ERROR); - break; - case NativeResponseCode.SERVICE_REGISTERED: - /* NNN regId serviceName regType */ - if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw); - clientInfo = getClientByRegistration(id); - if (clientInfo == null) break; - - servInfo = new DnsSdServiceInfo(cooked[2], null, null); - clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED, - id, 0, servInfo); - break; - case NativeResponseCode.SERVICE_REGISTRATION_FAILED: - /* NNN regId errorCode */ - if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw); - clientInfo = getClientByRegistration(id); - if (clientInfo == null) break; - - clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.ERROR); - break; - case NativeResponseCode.SERVICE_UPDATED: - /* NNN regId */ - break; - case NativeResponseCode.SERVICE_UPDATE_FAILED: - /* NNN regId errorCode */ - break; - case NativeResponseCode.SERVICE_RESOLVED: - /* NNN resolveId fullName hostName port txtlen txtdata */ - if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw); - clientInfo = getClientByResolve(id); - if (clientInfo == null) break; - - int index = cooked[2].indexOf("."); - if (index == -1) { - Slog.e(TAG, "Invalid service found " + raw); - break; - } - String name = cooked[2].substring(0, index); - String rest = cooked[2].substring(index); - String type = rest.replace(".local.", ""); - - clientInfo.mResolvedService = new DnsSdServiceInfo(name, type, null); - clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); - - stopResolveService(id); - getAddrInfo(id, cooked[3]); - break; - case NativeResponseCode.SERVICE_RESOLUTION_FAILED: - case NativeResponseCode.SERVICE_GET_ADDR_FAILED: - /* NNN resolveId errorCode */ - if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw); - clientInfo = getClientByResolve(id); - if (clientInfo == null) break; + clientInfo.mResolvedService.setServiceName(name); + clientInfo.mResolvedService.setServiceType(type); + clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); + stopResolveService(id); + if (!getAddrInfo(id, cooked[3])) { clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.ERROR); - break; - case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: - /* NNN resolveId hostname ttl addr */ - if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw); - clientInfo = getClientByResolve(id); - if (clientInfo == null || clientInfo.mResolvedService == null) break; - - try { - clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED, - clientInfo.mResolvedService); - clientInfo.mResolvedService = null; - clientInfo.mResolveId = INVALID_ID; - } catch (java.net.UnknownHostException e) { - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.ERROR); - } - stopGetAddrInfo(id); - break; - default: - break; - } - return false; + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + mIdToClientInfoMap.remove(id); + clientInfo.mResolvedService = null; + } + break; + case NativeResponseCode.SERVICE_RESOLUTION_FAILED: + /* NNN resolveId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw); + stopResolveService(id); + mIdToClientInfoMap.remove(id); + clientInfo.mResolvedService = null; + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + break; + case NativeResponseCode.SERVICE_GET_ADDR_FAILED: + /* NNN resolveId errorCode */ + stopGetAddrInfo(id); + mIdToClientInfoMap.remove(id); + clientInfo.mResolvedService = null; + if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw); + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + break; + case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: + /* NNN resolveId hostname ttl addr */ + if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw); + try { + clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED, + 0, clientId, clientInfo.mResolvedService); + } catch (java.net.UnknownHostException e) { + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.FAILURE_INTERNAL_ERROR, clientId); + } + stopGetAddrInfo(id); + mIdToClientInfoMap.remove(id); + clientInfo.mResolvedService = null; + break; + default: + break; } } @@ -579,7 +622,7 @@ public class NsdService extends INsdManager.Stub { return true; } - private boolean registerService(int regId, DnsSdServiceInfo service) { + private boolean registerService(int regId, NsdServiceInfo service) { if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service); try { //Add txtlen and txtdata @@ -637,7 +680,7 @@ public class NsdService extends INsdManager.Stub { return true; } - private boolean resolveService(int resolveId, DnsSdServiceInfo service) { + private boolean resolveService(int resolveId, NsdServiceInfo service) { if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service); try { mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(), @@ -700,49 +743,52 @@ public class NsdService extends INsdManager.Stub { mNsdStateMachine.dump(fd, pw, args); } - private ClientInfo getClientByDiscovery(int discoveryId) { - for (ClientInfo c: mClients.values()) { - if (c.mDiscoveryId == discoveryId) { - return c; - } - } - return null; + /* arg2 on the source message has an id that needs to be retained in replies + * see NsdManager for details */ + private Message obtainMessage(Message srcMsg) { + Message msg = Message.obtain(); + msg.arg2 = srcMsg.arg2; + return msg; } - private ClientInfo getClientByResolve(int resolveId) { - for (ClientInfo c: mClients.values()) { - if (c.mResolveId == resolveId) { - return c; - } - } - return null; + private void replyToMessage(Message msg, int what) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessage(msg); + dstMsg.what = what; + mReplyChannel.replyToMessage(msg, dstMsg); } - private ClientInfo getClientByRegistration(int regId) { - for (ClientInfo c: mClients.values()) { - if (c.mRegisteredIds.contains(regId)) { - return c; - } - } - return null; + private void replyToMessage(Message msg, int what, int arg1) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessage(msg); + dstMsg.what = what; + dstMsg.arg1 = arg1; + mReplyChannel.replyToMessage(msg, dstMsg); + } + + private void replyToMessage(Message msg, int what, Object obj) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessage(msg); + dstMsg.what = what; + dstMsg.obj = obj; + mReplyChannel.replyToMessage(msg, dstMsg); } /* Information tracked per client */ private class ClientInfo { - private static final int MAX_REG = 5; + private static final int MAX_LIMIT = 10; private AsyncChannel mChannel; private Messenger mMessenger; - private int mDiscoveryId; - private int mResolveId; /* Remembers a resolved service until getaddrinfo completes */ - private DnsSdServiceInfo mResolvedService; - private ArrayList mRegisteredIds = new ArrayList(); + private NsdServiceInfo mResolvedService; + + /* A map from client id to unique id sent to mDns */ + private SparseArray mClientIds = new SparseArray(); private ClientInfo(AsyncChannel c, Messenger m) { mChannel = c; mMessenger = m; - mDiscoveryId = mResolveId = INVALID_ID; if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m); } @@ -751,11 +797,10 @@ public class NsdService extends INsdManager.Stub { StringBuffer sb = new StringBuffer(); sb.append("mChannel ").append(mChannel).append("\n"); sb.append("mMessenger ").append(mMessenger).append("\n"); - sb.append("mDiscoveryId ").append(mDiscoveryId).append("\n"); - sb.append("mResolveId ").append(mResolveId).append("\n"); sb.append("mResolvedService ").append(mResolvedService).append("\n"); - for(int regId : mRegisteredIds) { - sb.append("regId ").append(regId).append("\n"); + for(int i = 0; i< mClientIds.size(); i++) { + sb.append("clientId ").append(mClientIds.keyAt(i)); + sb.append(" mDnsId ").append(mClientIds.valueAt(i)).append("\n"); } return sb.toString(); }