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 static com.android.internal.util.Preconditions.checkArgument;
20 import static com.android.internal.util.Preconditions.checkNotNull;
21 import static com.android.internal.util.Preconditions.checkStringNotEmpty;
22 
23 import android.annotation.SdkConstant;
24 import android.annotation.SdkConstant.SdkConstantType;
25 import android.annotation.SystemService;
26 import android.content.Context;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.Messenger;
32 import android.os.RemoteException;
33 import android.util.Log;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.AsyncChannel;
38 import com.android.internal.util.Protocol;
39 
40 import java.util.concurrent.CountDownLatch;
41 
42 /**
43  * The Network Service Discovery Manager class provides the API to discover services
44  * on a network. As an example, if device A and device B are connected over a Wi-Fi
45  * network, a game registered on device A can be discovered by a game on device
46  * B. Another example use case is an application discovering printers on the network.
47  *
48  * <p> The API currently supports DNS based service discovery and discovery is currently
49  * limited to a local network over Multicast DNS. DNS service discovery is described at
50  * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
51  *
52  * <p> The API is asynchronous, and responses to requests from an application are on listener
53  * callbacks on a separate internal thread.
54  *
55  * <p> There are three main operations the API supports - registration, discovery and resolution.
56  * <pre>
57  *                          Application start
58  *                                 |
59  *                                 |
60  *                                 |                  onServiceRegistered()
61  *                     Register any local services  /
62  *                      to be advertised with       \
63  *                       registerService()            onRegistrationFailed()
64  *                                 |
65  *                                 |
66  *                          discoverServices()
67  *                                 |
68  *                      Maintain a list to track
69  *                        discovered services
70  *                                 |
71  *                                 |--------->
72  *                                 |          |
73  *                                 |      onServiceFound()
74  *                                 |          |
75  *                                 |     add service to list
76  *                                 |          |
77  *                                 |<----------
78  *                                 |
79  *                                 |--------->
80  *                                 |          |
81  *                                 |      onServiceLost()
82  *                                 |          |
83  *                                 |   remove service from list
84  *                                 |          |
85  *                                 |<----------
86  *                                 |
87  *                                 |
88  *                                 | Connect to a service
89  *                                 | from list ?
90  *                                 |
91  *                          resolveService()
92  *                                 |
93  *                         onServiceResolved()
94  *                                 |
95  *                     Establish connection to service
96  *                     with the host and port information
97  *
98  * </pre>
99  * An application that needs to advertise itself over a network for other applications to
100  * discover it can do so with a call to {@link #registerService}. If Example is a http based
101  * application that can provide HTML data to peer services, it can register a name "Example"
102  * with service type "_http._tcp". A successful registration is notified with a callback to
103  * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified
104  * over {@link RegistrationListener#onRegistrationFailed}
105  *
106  * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
107  * with a call to {@link #discoverServices}. A service found is notified with a callback
108  * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
109  * {@link DiscoveryListener#onServiceLost}.
110  *
111  * <p> Once the peer application discovers the "Example" http service, and either needs to read the
112  * attributes of the service or wants to receive data from the "Example" application, it can
113  * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port
114  * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a
115  * failure is notified on {@link ResolveListener#onResolveFailed}.
116  *
117  * Applications can reserve for a service type at
118  * http://www.iana.org/form/ports-service. Existing services can be found at
119  * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
120  *
121  * {@see NsdServiceInfo}
122  */
123 @SystemService(Context.NSD_SERVICE)
124 public final class NsdManager {
125     private static final String TAG = NsdManager.class.getSimpleName();
126     private static final boolean DBG = false;
127 
128     /**
129      * Broadcast intent action to indicate whether network service discovery is
130      * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
131      * information as int.
132      *
133      * @see #EXTRA_NSD_STATE
134      */
135     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
136     public static final String ACTION_NSD_STATE_CHANGED = "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 static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
214     static {
EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES")215         EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED")216         EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED");
EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED")217         EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED");
EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND")218         EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST")219         EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY")220         EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY");
EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED")221         EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED");
EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED")222         EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED");
EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE")223         EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE");
EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED")224         EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED");
EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED")225         EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED");
EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE")226         EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE");
EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED")227         EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED");
EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED")228         EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED");
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE")229         EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED")230         EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED")231         EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
EVENT_NAMES.put(ENABLE, "ENABLE")232         EVENT_NAMES.put(ENABLE, "ENABLE");
EVENT_NAMES.put(DISABLE, "DISABLE")233         EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT")234         EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
235     }
236 
237     /** @hide */
nameOf(int event)238     public static String nameOf(int event) {
239         String name = EVENT_NAMES.get(event);
240         if (name == null) {
241             return Integer.toString(event);
242         }
243         return name;
244     }
245 
246     private static final int FIRST_LISTENER_KEY = 1;
247 
248     private final INsdManager mService;
249     private final Context mContext;
250 
251     private int mListenerKey = FIRST_LISTENER_KEY;
252     private final SparseArray mListenerMap = new SparseArray();
253     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
254     private final Object mMapLock = new Object();
255 
256     private final AsyncChannel mAsyncChannel = new AsyncChannel();
257     private ServiceHandler mHandler;
258     private final CountDownLatch mConnected = new CountDownLatch(1);
259 
260     /**
261      * Create a new Nsd instance. Applications use
262      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
263      * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}.
264      * @param service the Binder interface
265      * @hide - hide this because it takes in a parameter of type INsdManager, which
266      * is a system private class.
267      */
NsdManager(Context context, INsdManager service)268     public NsdManager(Context context, INsdManager service) {
269         mService = service;
270         mContext = context;
271         init();
272     }
273 
274     /**
275      * @hide
276      */
277     @VisibleForTesting
disconnect()278     public void disconnect() {
279         mAsyncChannel.disconnect();
280         mHandler.getLooper().quitSafely();
281     }
282 
283     /**
284      * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
285      * {@link RegistrationListener#onUnregistrationFailed},
286      * {@link DiscoveryListener#onStartDiscoveryFailed},
287      * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}.
288      *
289      * Indicates that the operation failed due to an internal error.
290      */
291     public static final int FAILURE_INTERNAL_ERROR               = 0;
292 
293     /**
294      * Indicates that the operation failed because it is already active.
295      */
296     public static final int FAILURE_ALREADY_ACTIVE              = 3;
297 
298     /**
299      * Indicates that the operation failed because the maximum outstanding
300      * requests from the applications have reached.
301      */
302     public static final int FAILURE_MAX_LIMIT                   = 4;
303 
304     /** Interface for callback invocation for service discovery */
305     public interface DiscoveryListener {
306 
onStartDiscoveryFailed(String serviceType, int errorCode)307         public void onStartDiscoveryFailed(String serviceType, int errorCode);
308 
onStopDiscoveryFailed(String serviceType, int errorCode)309         public void onStopDiscoveryFailed(String serviceType, int errorCode);
310 
onDiscoveryStarted(String serviceType)311         public void onDiscoveryStarted(String serviceType);
312 
onDiscoveryStopped(String serviceType)313         public void onDiscoveryStopped(String serviceType);
314 
onServiceFound(NsdServiceInfo serviceInfo)315         public void onServiceFound(NsdServiceInfo serviceInfo);
316 
onServiceLost(NsdServiceInfo serviceInfo)317         public void onServiceLost(NsdServiceInfo serviceInfo);
318     }
319 
320     /** Interface for callback invocation for service registration */
321     public interface RegistrationListener {
322 
onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode)323         public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
324 
onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode)325         public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
326 
onServiceRegistered(NsdServiceInfo serviceInfo)327         public void onServiceRegistered(NsdServiceInfo serviceInfo);
328 
onServiceUnregistered(NsdServiceInfo serviceInfo)329         public void onServiceUnregistered(NsdServiceInfo serviceInfo);
330     }
331 
332     /** Interface for callback invocation for service resolution */
333     public interface ResolveListener {
334 
onResolveFailed(NsdServiceInfo serviceInfo, int errorCode)335         public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
336 
onServiceResolved(NsdServiceInfo serviceInfo)337         public void onServiceResolved(NsdServiceInfo serviceInfo);
338     }
339 
340     @VisibleForTesting
341     class ServiceHandler extends Handler {
ServiceHandler(Looper looper)342         ServiceHandler(Looper looper) {
343             super(looper);
344         }
345 
346         @Override
handleMessage(Message message)347         public void handleMessage(Message message) {
348             final int what = message.what;
349             final int key = message.arg2;
350             switch (what) {
351                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
352                     mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
353                     return;
354                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
355                     mConnected.countDown();
356                     return;
357                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
358                     Log.e(TAG, "Channel lost");
359                     return;
360                 default:
361                     break;
362             }
363             final Object listener;
364             final NsdServiceInfo ns;
365             synchronized (mMapLock) {
366                 listener = mListenerMap.get(key);
367                 ns = mServiceMap.get(key);
368             }
369             if (listener == null) {
370                 Log.d(TAG, "Stale key " + message.arg2);
371                 return;
372             }
373             if (DBG) {
374                 Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
375             }
376             switch (what) {
377                 case DISCOVER_SERVICES_STARTED:
378                     String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
379                     ((DiscoveryListener) listener).onDiscoveryStarted(s);
380                     break;
381                 case DISCOVER_SERVICES_FAILED:
382                     removeListener(key);
383                     ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
384                             message.arg1);
385                     break;
386                 case SERVICE_FOUND:
387                     ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
388                     break;
389                 case SERVICE_LOST:
390                     ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
391                     break;
392                 case STOP_DISCOVERY_FAILED:
393                     // TODO: failure to stop discovery should be internal and retried internally, as
394                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
395                     removeListener(key);
396                     ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
397                             message.arg1);
398                     break;
399                 case STOP_DISCOVERY_SUCCEEDED:
400                     removeListener(key);
401                     ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
402                     break;
403                 case REGISTER_SERVICE_FAILED:
404                     removeListener(key);
405                     ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
406                     break;
407                 case REGISTER_SERVICE_SUCCEEDED:
408                     ((RegistrationListener) listener).onServiceRegistered(
409                             (NsdServiceInfo) message.obj);
410                     break;
411                 case UNREGISTER_SERVICE_FAILED:
412                     removeListener(key);
413                     ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
414                     break;
415                 case UNREGISTER_SERVICE_SUCCEEDED:
416                     // TODO: do not unregister listener until service is unregistered, or provide
417                     // alternative way for unregistering ?
418                     removeListener(message.arg2);
419                     ((RegistrationListener) listener).onServiceUnregistered(ns);
420                     break;
421                 case RESOLVE_SERVICE_FAILED:
422                     removeListener(key);
423                     ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
424                     break;
425                 case RESOLVE_SERVICE_SUCCEEDED:
426                     removeListener(key);
427                     ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
428                     break;
429                 default:
430                     Log.d(TAG, "Ignored " + message);
431                     break;
432             }
433         }
434     }
435 
nextListenerKey()436     private int nextListenerKey() {
437         // Ensure mListenerKey >= FIRST_LISTENER_KEY;
438         mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
439         return mListenerKey;
440     }
441 
442     // Assert that the listener is not in the map, then add it and returns its key
putListener(Object listener, NsdServiceInfo s)443     private int putListener(Object listener, NsdServiceInfo s) {
444         checkListener(listener);
445         final int key;
446         synchronized (mMapLock) {
447             int valueIndex = mListenerMap.indexOfValue(listener);
448             checkArgument(valueIndex == -1, "listener already in use");
449             key = nextListenerKey();
450             mListenerMap.put(key, listener);
451             mServiceMap.put(key, s);
452         }
453         return key;
454     }
455 
removeListener(int key)456     private void removeListener(int key) {
457         synchronized (mMapLock) {
458             mListenerMap.remove(key);
459             mServiceMap.remove(key);
460         }
461     }
462 
getListenerKey(Object listener)463     private int getListenerKey(Object listener) {
464         checkListener(listener);
465         synchronized (mMapLock) {
466             int valueIndex = mListenerMap.indexOfValue(listener);
467             checkArgument(valueIndex != -1, "listener not registered");
468             return mListenerMap.keyAt(valueIndex);
469         }
470     }
471 
getNsdServiceInfoType(NsdServiceInfo s)472     private static String getNsdServiceInfoType(NsdServiceInfo s) {
473         if (s == null) return "?";
474         return s.getServiceType();
475     }
476 
477     /**
478      * Initialize AsyncChannel
479      */
init()480     private void init() {
481         final Messenger messenger = getMessenger();
482         if (messenger == null) {
483             fatal("Failed to obtain service Messenger");
484         }
485         HandlerThread t = new HandlerThread("NsdManager");
486         t.start();
487         mHandler = new ServiceHandler(t.getLooper());
488         mAsyncChannel.connect(mContext, mHandler, messenger);
489         try {
490             mConnected.await();
491         } catch (InterruptedException e) {
492             fatal("Interrupted wait at init");
493         }
494     }
495 
fatal(String msg)496     private static void fatal(String msg) {
497         Log.e(TAG, msg);
498         throw new RuntimeException(msg);
499     }
500 
501     /**
502      * Register a service to be discovered by other services.
503      *
504      * <p> The function call immediately returns after sending a request to register service
505      * to the framework. The application is notified of a successful registration
506      * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
507      * through {@link RegistrationListener#onRegistrationFailed}.
508      *
509      * <p> The application should call {@link #unregisterService} when the service
510      * registration is no longer required, and/or whenever the application is stopped.
511      *
512      * @param serviceInfo The service being registered
513      * @param protocolType The service discovery protocol
514      * @param listener The listener notifies of a successful registration and is used to
515      * unregister this service through a call on {@link #unregisterService}. Cannot be null.
516      * Cannot be in use for an active service registration.
517      */
registerService(NsdServiceInfo serviceInfo, int protocolType, RegistrationListener listener)518     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
519             RegistrationListener listener) {
520         checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
521         checkServiceInfo(serviceInfo);
522         checkProtocol(protocolType);
523         int key = putListener(listener, serviceInfo);
524         mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
525     }
526 
527     /**
528      * Unregister a service registered through {@link #registerService}. A successful
529      * unregister is notified to the application with a call to
530      * {@link RegistrationListener#onServiceUnregistered}.
531      *
532      * @param listener This should be the listener object that was passed to
533      * {@link #registerService}. It identifies the service that should be unregistered
534      * and notifies of a successful or unsuccessful unregistration via the listener
535      * callbacks.  In API versions 20 and above, the listener object may be used for
536      * another service registration once the callback has been called.  In API versions <= 19,
537      * there is no entirely reliable way to know when a listener may be re-used, and a new
538      * listener should be created for each service registration request.
539      */
unregisterService(RegistrationListener listener)540     public void unregisterService(RegistrationListener listener) {
541         int id = getListenerKey(listener);
542         mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
543     }
544 
545     /**
546      * Initiate service discovery to browse for instances of a service type. Service discovery
547      * consumes network bandwidth and will continue until the application calls
548      * {@link #stopServiceDiscovery}.
549      *
550      * <p> The function call immediately returns after sending a request to start service
551      * discovery to the framework. The application is notified of a success to initiate
552      * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
553      * through {@link DiscoveryListener#onStartDiscoveryFailed}.
554      *
555      * <p> Upon successful start, application is notified when a service is found with
556      * {@link DiscoveryListener#onServiceFound} or when a service is lost with
557      * {@link DiscoveryListener#onServiceLost}.
558      *
559      * <p> Upon failure to start, service discovery is not active and application does
560      * not need to invoke {@link #stopServiceDiscovery}
561      *
562      * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
563      * service type is no longer required, and/or whenever the application is paused or
564      * stopped.
565      *
566      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
567      * http services or "_ipp._tcp" for printers
568      * @param protocolType The service discovery protocol
569      * @param listener  The listener notifies of a successful discovery and is used
570      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
571      * Cannot be null. Cannot be in use for an active service discovery.
572      */
discoverServices(String serviceType, int protocolType, DiscoveryListener listener)573     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
574         checkStringNotEmpty(serviceType, "Service type cannot be empty");
575         checkProtocol(protocolType);
576 
577         NsdServiceInfo s = new NsdServiceInfo();
578         s.setServiceType(serviceType);
579 
580         int key = putListener(listener, s);
581         mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
582     }
583 
584     /**
585      * Stop service discovery initiated with {@link #discoverServices}.  An active service
586      * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
587      * and it stays active until the application invokes a stop service discovery. A successful
588      * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
589      *
590      * <p> Upon failure to stop service discovery, application is notified through
591      * {@link DiscoveryListener#onStopDiscoveryFailed}.
592      *
593      * @param listener This should be the listener object that was passed to {@link #discoverServices}.
594      * It identifies the discovery that should be stopped and notifies of a successful or
595      * unsuccessful stop.  In API versions 20 and above, the listener object may be used for
596      * another service discovery once the callback has been called.  In API versions <= 19,
597      * there is no entirely reliable way to know when a listener may be re-used, and a new
598      * listener should be created for each service discovery request.
599      */
stopServiceDiscovery(DiscoveryListener listener)600     public void stopServiceDiscovery(DiscoveryListener listener) {
601         int id = getListenerKey(listener);
602         mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
603     }
604 
605     /**
606      * Resolve a discovered service. An application can resolve a service right before
607      * establishing a connection to fetch the IP and port details on which to setup
608      * the connection.
609      *
610      * @param serviceInfo service to be resolved
611      * @param listener to receive callback upon success or failure. Cannot be null.
612      * Cannot be in use for an active service resolution.
613      */
resolveService(NsdServiceInfo serviceInfo, ResolveListener listener)614     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
615         checkServiceInfo(serviceInfo);
616         int key = putListener(listener, serviceInfo);
617         mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
618     }
619 
620     /** Internal use only @hide */
setEnabled(boolean enabled)621     public void setEnabled(boolean enabled) {
622         try {
623             mService.setEnabled(enabled);
624         } catch (RemoteException e) {
625             throw e.rethrowFromSystemServer();
626         }
627     }
628 
629     /**
630      * Get a reference to NsdService handler. This is used to establish
631      * an AsyncChannel communication with the service
632      *
633      * @return Messenger pointing to the NsdService handler
634      */
getMessenger()635     private Messenger getMessenger() {
636         try {
637             return mService.getMessenger();
638         } catch (RemoteException e) {
639             throw e.rethrowFromSystemServer();
640         }
641     }
642 
checkListener(Object listener)643     private static void checkListener(Object listener) {
644         checkNotNull(listener, "listener cannot be null");
645     }
646 
checkProtocol(int protocolType)647     private static void checkProtocol(int protocolType) {
648         checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
649     }
650 
checkServiceInfo(NsdServiceInfo serviceInfo)651     private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
652         checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
653         checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
654         checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
655     }
656 }
657