• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.nsd;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.content.Context;
22 import android.os.Binder;
23 import android.os.IBinder;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.os.Messenger;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.SparseArray;
33 
34 import java.util.concurrent.CountDownLatch;
35 
36 import com.android.internal.util.AsyncChannel;
37 import com.android.internal.util.Protocol;
38 
39 /**
40  * The Network Service Discovery Manager class provides the API to discover services
41  * on a network. As an example, if device A and device B are connected over a Wi-Fi
42  * network, a game registered on device A can be discovered by a game on device
43  * B. Another example use case is an application discovering printers on the network.
44  *
45  * <p> The API currently supports DNS based service discovery and discovery is currently
46  * limited to a local network over Multicast DNS. DNS service discovery is described at
47  * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
48  *
49  * <p> The API is asynchronous and responses to requests from an application are on listener
50  * callbacks on a seperate thread.
51  *
52  * <p> There are three main operations the API supports - registration, discovery and resolution.
53  * <pre>
54  *                          Application start
55  *                                 |
56  *                                 |
57  *                                 |                  onServiceRegistered()
58  *                     Register any local services  /
59  *                      to be advertised with       \
60  *                       registerService()            onRegistrationFailed()
61  *                                 |
62  *                                 |
63  *                          discoverServices()
64  *                                 |
65  *                      Maintain a list to track
66  *                        discovered services
67  *                                 |
68  *                                 |--------->
69  *                                 |          |
70  *                                 |      onServiceFound()
71  *                                 |          |
72  *                                 |     add service to list
73  *                                 |          |
74  *                                 |<----------
75  *                                 |
76  *                                 |--------->
77  *                                 |          |
78  *                                 |      onServiceLost()
79  *                                 |          |
80  *                                 |   remove service from list
81  *                                 |          |
82  *                                 |<----------
83  *                                 |
84  *                                 |
85  *                                 | Connect to a service
86  *                                 | from list ?
87  *                                 |
88  *                          resolveService()
89  *                                 |
90  *                         onServiceResolved()
91  *                                 |
92  *                     Establish connection to service
93  *                     with the host and port information
94  *
95  * </pre>
96  * An application that needs to advertise itself over a network for other applications to
97  * discover it can do so with a call to {@link #registerService}. If Example is a http based
98  * application that can provide HTML data to peer services, it can register a name "Example"
99  * with service type "_http._tcp". A successful registration is notified with a callback to
100  * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified
101  * over {@link RegistrationListener#onRegistrationFailed}
102  *
103  * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
104  * with a call to {@link #discoverServices}. A service found is notified with a callback
105  * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
106  * {@link DiscoveryListener#onServiceLost}.
107  *
108  * <p> Once the peer application discovers the "Example" http srevice, and needs to receive data
109  * from the "Example" application, it can initiate a resolve with {@link #resolveService} to
110  * resolve the host and port details for the purpose of establishing a connection. A successful
111  * resolve is notified on {@link ResolveListener#onServiceResolved} and a failure is notified
112  * on {@link ResolveListener#onResolveFailed}.
113  *
114  * Applications can reserve for a service type at
115  * http://www.iana.org/form/ports-service. Existing services can be found at
116  * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
117  *
118  * Get an instance of this class by calling {@link android.content.Context#getSystemService(String)
119  * Context.getSystemService(Context.NSD_SERVICE)}.
120  *
121  * {@see NsdServiceInfo}
122  */
123 public final class NsdManager {
124     private static final String TAG = "NsdManager";
125     INsdManager mService;
126 
127     /**
128      * Broadcast intent action to indicate whether network service discovery is
129      * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
130      * information as int.
131      *
132      * @see #EXTRA_NSD_STATE
133      */
134     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
135     public static final String ACTION_NSD_STATE_CHANGED =
136         "android.net.nsd.STATE_CHANGED";
137 
138     /**
139      * The lookup key for an int that indicates whether network service discovery is enabled
140      * or disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
141      *
142      * @see #NSD_STATE_DISABLED
143      * @see #NSD_STATE_ENABLED
144      */
145     public static final String EXTRA_NSD_STATE = "nsd_state";
146 
147     /**
148      * Network service discovery is disabled
149      *
150      * @see #ACTION_NSD_STATE_CHANGED
151      */
152     public static final int NSD_STATE_DISABLED = 1;
153 
154     /**
155      * Network service discovery is enabled
156      *
157      * @see #ACTION_NSD_STATE_CHANGED
158      */
159     public static final int NSD_STATE_ENABLED = 2;
160 
161     private static final int BASE = Protocol.BASE_NSD_MANAGER;
162 
163     /** @hide */
164     public static final int DISCOVER_SERVICES                       = BASE + 1;
165     /** @hide */
166     public static final int DISCOVER_SERVICES_STARTED               = BASE + 2;
167     /** @hide */
168     public static final int DISCOVER_SERVICES_FAILED                = BASE + 3;
169     /** @hide */
170     public static final int SERVICE_FOUND                           = BASE + 4;
171     /** @hide */
172     public static final int SERVICE_LOST                            = BASE + 5;
173 
174     /** @hide */
175     public static final int STOP_DISCOVERY                          = BASE + 6;
176     /** @hide */
177     public static final int STOP_DISCOVERY_FAILED                   = BASE + 7;
178     /** @hide */
179     public static final int STOP_DISCOVERY_SUCCEEDED                = BASE + 8;
180 
181     /** @hide */
182     public static final int REGISTER_SERVICE                        = BASE + 9;
183     /** @hide */
184     public static final int REGISTER_SERVICE_FAILED                 = BASE + 10;
185     /** @hide */
186     public static final int REGISTER_SERVICE_SUCCEEDED              = BASE + 11;
187 
188     /** @hide */
189     public static final int UNREGISTER_SERVICE                      = BASE + 12;
190     /** @hide */
191     public static final int UNREGISTER_SERVICE_FAILED               = BASE + 13;
192     /** @hide */
193     public static final int UNREGISTER_SERVICE_SUCCEEDED            = BASE + 14;
194 
195     /** @hide */
196     public static final int RESOLVE_SERVICE                         = BASE + 18;
197     /** @hide */
198     public static final int RESOLVE_SERVICE_FAILED                  = BASE + 19;
199     /** @hide */
200     public static final int RESOLVE_SERVICE_SUCCEEDED               = BASE + 20;
201 
202     /** @hide */
203     public static final int ENABLE                                  = BASE + 24;
204     /** @hide */
205     public static final int DISABLE                                 = BASE + 25;
206 
207     /** @hide */
208     public static final int NATIVE_DAEMON_EVENT                     = BASE + 26;
209 
210     /** Dns based service discovery protocol */
211     public static final int PROTOCOL_DNS_SD = 0x0001;
212 
213     private Context mContext;
214 
215     private static final int INVALID_LISTENER_KEY = 0;
216     private int mListenerKey = 1;
217     private final SparseArray mListenerMap = new SparseArray();
218     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<NsdServiceInfo>();
219     private final Object mMapLock = new Object();
220 
221     private final AsyncChannel mAsyncChannel = new AsyncChannel();
222     private ServiceHandler mHandler;
223     private final CountDownLatch mConnected = new CountDownLatch(1);
224 
225     /**
226      * Create a new Nsd instance. Applications use
227      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
228      * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}.
229      * @param service the Binder interface
230      * @hide - hide this because it takes in a parameter of type INsdManager, which
231      * is a system private class.
232      */
NsdManager(Context context, INsdManager service)233     public NsdManager(Context context, INsdManager service) {
234         mService = service;
235         mContext = context;
236         init();
237     }
238 
239     /**
240      * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
241      * {@link RegistrationListener#onUnregistrationFailed},
242      * {@link DiscoveryListener#onStartDiscoveryFailed},
243      * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}.
244      *
245      * Indicates that the operation failed due to an internal error.
246      */
247     public static final int FAILURE_INTERNAL_ERROR               = 0;
248 
249     /**
250      * Indicates that the operation failed because it is already active.
251      */
252     public static final int FAILURE_ALREADY_ACTIVE              = 3;
253 
254     /**
255      * Indicates that the operation failed because the maximum outstanding
256      * requests from the applications have reached.
257      */
258     public static final int FAILURE_MAX_LIMIT                   = 4;
259 
260     /** Interface for callback invocation for service discovery */
261     public interface DiscoveryListener {
262 
onStartDiscoveryFailed(String serviceType, int errorCode)263         public void onStartDiscoveryFailed(String serviceType, int errorCode);
264 
onStopDiscoveryFailed(String serviceType, int errorCode)265         public void onStopDiscoveryFailed(String serviceType, int errorCode);
266 
onDiscoveryStarted(String serviceType)267         public void onDiscoveryStarted(String serviceType);
268 
onDiscoveryStopped(String serviceType)269         public void onDiscoveryStopped(String serviceType);
270 
onServiceFound(NsdServiceInfo serviceInfo)271         public void onServiceFound(NsdServiceInfo serviceInfo);
272 
onServiceLost(NsdServiceInfo serviceInfo)273         public void onServiceLost(NsdServiceInfo serviceInfo);
274 
275     }
276 
277     /** Interface for callback invocation for service registration */
278     public interface RegistrationListener {
279 
onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode)280         public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
281 
onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode)282         public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
283 
onServiceRegistered(NsdServiceInfo serviceInfo)284         public void onServiceRegistered(NsdServiceInfo serviceInfo);
285 
onServiceUnregistered(NsdServiceInfo serviceInfo)286         public void onServiceUnregistered(NsdServiceInfo serviceInfo);
287     }
288 
289     /** Interface for callback invocation for service resolution */
290     public interface ResolveListener {
291 
onResolveFailed(NsdServiceInfo serviceInfo, int errorCode)292         public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
293 
onServiceResolved(NsdServiceInfo serviceInfo)294         public void onServiceResolved(NsdServiceInfo serviceInfo);
295     }
296 
297     private class ServiceHandler extends Handler {
ServiceHandler(Looper looper)298         ServiceHandler(Looper looper) {
299             super(looper);
300         }
301 
302         @Override
handleMessage(Message message)303         public void handleMessage(Message message) {
304             Object listener = getListener(message.arg2);
305             boolean listenerRemove = true;
306             switch (message.what) {
307                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
308                     mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
309                     mConnected.countDown();
310                     break;
311                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
312                     // Ignore
313                     break;
314                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
315                     Log.e(TAG, "Channel lost");
316                     break;
317                 case DISCOVER_SERVICES_STARTED:
318                     String s = ((NsdServiceInfo) message.obj).getServiceType();
319                     ((DiscoveryListener) listener).onDiscoveryStarted(s);
320                     // Keep listener until stop discovery
321                     listenerRemove = false;
322                     break;
323                 case DISCOVER_SERVICES_FAILED:
324                     ((DiscoveryListener) listener).onStartDiscoveryFailed(
325                             getNsdService(message.arg2).getServiceType(), message.arg1);
326                     break;
327                 case SERVICE_FOUND:
328                     ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
329                     // Keep listener until stop discovery
330                     listenerRemove = false;
331                     break;
332                 case SERVICE_LOST:
333                     ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
334                     // Keep listener until stop discovery
335                     listenerRemove = false;
336                     break;
337                 case STOP_DISCOVERY_FAILED:
338                     ((DiscoveryListener) listener).onStopDiscoveryFailed(
339                             getNsdService(message.arg2).getServiceType(), message.arg1);
340                     break;
341                 case STOP_DISCOVERY_SUCCEEDED:
342                     ((DiscoveryListener) listener).onDiscoveryStopped(
343                             getNsdService(message.arg2).getServiceType());
344                     break;
345                 case REGISTER_SERVICE_FAILED:
346                     ((RegistrationListener) listener).onRegistrationFailed(
347                             getNsdService(message.arg2), message.arg1);
348                     break;
349                 case REGISTER_SERVICE_SUCCEEDED:
350                     ((RegistrationListener) listener).onServiceRegistered(
351                             (NsdServiceInfo) message.obj);
352                     // Keep listener until unregister
353                     listenerRemove = false;
354                     break;
355                 case UNREGISTER_SERVICE_FAILED:
356                     ((RegistrationListener) listener).onUnregistrationFailed(
357                             getNsdService(message.arg2), message.arg1);
358                     break;
359                 case UNREGISTER_SERVICE_SUCCEEDED:
360                     ((RegistrationListener) listener).onServiceUnregistered(
361                             getNsdService(message.arg2));
362                     break;
363                 case RESOLVE_SERVICE_FAILED:
364                     ((ResolveListener) listener).onResolveFailed(
365                             getNsdService(message.arg2), message.arg1);
366                     break;
367                 case RESOLVE_SERVICE_SUCCEEDED:
368                     ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
369                     break;
370                 default:
371                     Log.d(TAG, "Ignored " + message);
372                     break;
373             }
374             if (listenerRemove) {
375                 removeListener(message.arg2);
376             }
377         }
378     }
379 
putListener(Object listener, NsdServiceInfo s)380     private int putListener(Object listener, NsdServiceInfo s) {
381         if (listener == null) return INVALID_LISTENER_KEY;
382         int key;
383         synchronized (mMapLock) {
384             do {
385                 key = mListenerKey++;
386             } while (key == INVALID_LISTENER_KEY);
387             mListenerMap.put(key, listener);
388             mServiceMap.put(key, s);
389         }
390         return key;
391     }
392 
getListener(int key)393     private Object getListener(int key) {
394         if (key == INVALID_LISTENER_KEY) return null;
395         synchronized (mMapLock) {
396             return mListenerMap.get(key);
397         }
398     }
399 
getNsdService(int key)400     private NsdServiceInfo getNsdService(int key) {
401         synchronized (mMapLock) {
402             return mServiceMap.get(key);
403         }
404     }
405 
removeListener(int key)406     private void removeListener(int key) {
407         if (key == INVALID_LISTENER_KEY) return;
408         synchronized (mMapLock) {
409             mListenerMap.remove(key);
410             mServiceMap.remove(key);
411         }
412     }
413 
getListenerKey(Object listener)414     private int getListenerKey(Object listener) {
415         synchronized (mMapLock) {
416             int valueIndex = mListenerMap.indexOfValue(listener);
417             if (valueIndex != -1) {
418                 return mListenerMap.keyAt(valueIndex);
419             }
420         }
421         return INVALID_LISTENER_KEY;
422     }
423 
424 
425     /**
426      * Initialize AsyncChannel
427      */
init()428     private void init() {
429         final Messenger messenger = getMessenger();
430         if (messenger == null) throw new RuntimeException("Failed to initialize");
431         HandlerThread t = new HandlerThread("NsdManager");
432         t.start();
433         mHandler = new ServiceHandler(t.getLooper());
434         mAsyncChannel.connect(mContext, mHandler, messenger);
435         try {
436             mConnected.await();
437         } catch (InterruptedException e) {
438             Log.e(TAG, "interrupted wait at init");
439         }
440     }
441 
442     /**
443      * Register a service to be discovered by other services.
444      *
445      * <p> The function call immediately returns after sending a request to register service
446      * to the framework. The application is notified of a success to initiate
447      * discovery through the callback {@link RegistrationListener#onServiceRegistered} or a failure
448      * through {@link RegistrationListener#onRegistrationFailed}.
449      *
450      * @param serviceInfo The service being registered
451      * @param protocolType The service discovery protocol
452      * @param listener The listener notifies of a successful registration and is used to
453      * unregister this service through a call on {@link #unregisterService}. Cannot be null.
454      */
registerService(NsdServiceInfo serviceInfo, int protocolType, RegistrationListener listener)455     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
456             RegistrationListener listener) {
457         if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
458                 TextUtils.isEmpty(serviceInfo.getServiceType())) {
459             throw new IllegalArgumentException("Service name or type cannot be empty");
460         }
461         if (serviceInfo.getPort() <= 0) {
462             throw new IllegalArgumentException("Invalid port number");
463         }
464         if (listener == null) {
465             throw new IllegalArgumentException("listener cannot be null");
466         }
467         if (protocolType != PROTOCOL_DNS_SD) {
468             throw new IllegalArgumentException("Unsupported protocol");
469         }
470         mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, putListener(listener, serviceInfo),
471                 serviceInfo);
472     }
473 
474     /**
475      * Unregister a service registered through {@link #registerService}. A successful
476      * unregister is notified to the application with a call to
477      * {@link RegistrationListener#onServiceUnregistered}.
478      *
479      * @param listener This should be the listener object that was passed to
480      * {@link #registerService}. It identifies the service that should be unregistered
481      * and notifies of a successful unregistration.
482      */
unregisterService(RegistrationListener listener)483     public void unregisterService(RegistrationListener listener) {
484         int id = getListenerKey(listener);
485         if (id == INVALID_LISTENER_KEY) {
486             throw new IllegalArgumentException("listener not registered");
487         }
488         if (listener == null) {
489             throw new IllegalArgumentException("listener cannot be null");
490         }
491         mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
492     }
493 
494     /**
495      * Initiate service discovery to browse for instances of a service type. Service discovery
496      * consumes network bandwidth and will continue until the application calls
497      * {@link #stopServiceDiscovery}.
498      *
499      * <p> The function call immediately returns after sending a request to start service
500      * discovery to the framework. The application is notified of a success to initiate
501      * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
502      * through {@link DiscoveryListener#onStartDiscoveryFailed}.
503      *
504      * <p> Upon successful start, application is notified when a service is found with
505      * {@link DiscoveryListener#onServiceFound} or when a service is lost with
506      * {@link DiscoveryListener#onServiceLost}.
507      *
508      * <p> Upon failure to start, service discovery is not active and application does
509      * not need to invoke {@link #stopServiceDiscovery}
510      *
511      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
512      * http services or "_ipp._tcp" for printers
513      * @param protocolType The service discovery protocol
514      * @param listener  The listener notifies of a successful discovery and is used
515      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
516      * Cannot be null.
517      */
discoverServices(String serviceType, int protocolType, DiscoveryListener listener)518     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
519         if (listener == null) {
520             throw new IllegalArgumentException("listener cannot be null");
521         }
522         if (TextUtils.isEmpty(serviceType)) {
523             throw new IllegalArgumentException("Service type cannot be empty");
524         }
525 
526         if (protocolType != PROTOCOL_DNS_SD) {
527             throw new IllegalArgumentException("Unsupported protocol");
528         }
529 
530         NsdServiceInfo s = new NsdServiceInfo();
531         s.setServiceType(serviceType);
532         mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, putListener(listener, s), s);
533     }
534 
535     /**
536      * Stop service discovery initiated with {@link #discoverServices}. An active service
537      * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
538      * and it stays active until the application invokes a stop service discovery. A successful
539      * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
540      *
541      * <p> Upon failure to stop service discovery, application is notified through
542      * {@link DiscoveryListener#onStopDiscoveryFailed}.
543      *
544      * @param listener This should be the listener object that was passed to {@link #discoverServices}.
545      * It identifies the discovery that should be stopped and notifies of a successful stop.
546      */
stopServiceDiscovery(DiscoveryListener listener)547     public void stopServiceDiscovery(DiscoveryListener listener) {
548         int id = getListenerKey(listener);
549         if (id == INVALID_LISTENER_KEY) {
550             throw new IllegalArgumentException("service discovery not active on listener");
551         }
552         if (listener == null) {
553             throw new IllegalArgumentException("listener cannot be null");
554         }
555         mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
556     }
557 
558     /**
559      * Resolve a discovered service. An application can resolve a service right before
560      * establishing a connection to fetch the IP and port details on which to setup
561      * the connection.
562      *
563      * @param serviceInfo service to be resolved
564      * @param listener to receive callback upon success or failure. Cannot be null.
565      */
resolveService(NsdServiceInfo serviceInfo, ResolveListener listener)566     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
567         if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
568                 TextUtils.isEmpty(serviceInfo.getServiceType())) {
569             throw new IllegalArgumentException("Service name or type cannot be empty");
570         }
571         if (listener == null) {
572             throw new IllegalArgumentException("listener cannot be null");
573         }
574         mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, putListener(listener, serviceInfo),
575                 serviceInfo);
576     }
577 
578     /** Internal use only @hide */
setEnabled(boolean enabled)579     public void setEnabled(boolean enabled) {
580         try {
581             mService.setEnabled(enabled);
582         } catch (RemoteException e) { }
583     }
584 
585     /**
586      * Get a reference to NetworkService handler. This is used to establish
587      * an AsyncChannel communication with the service
588      *
589      * @return Messenger pointing to the NetworkService handler
590      */
getMessenger()591     private Messenger getMessenger() {
592         try {
593             return mService.getMessenger();
594         } catch (RemoteException e) {
595             return null;
596         }
597     }
598 }
599