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