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