• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SdkConstant;
23 import android.annotation.SdkConstant.SdkConstantType;
24 import android.annotation.SystemService;
25 import android.app.compat.CompatChanges;
26 import android.compat.annotation.ChangeId;
27 import android.compat.annotation.EnabledSince;
28 import android.content.Context;
29 import android.net.ConnectivityManager;
30 import android.net.ConnectivityManager.NetworkCallback;
31 import android.net.Network;
32 import android.net.NetworkRequest;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Log;
42 import android.util.SparseArray;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 
47 import java.util.Objects;
48 import java.util.concurrent.Executor;
49 
50 /**
51  * The Network Service Discovery Manager class provides the API to discover services
52  * on a network. As an example, if device A and device B are connected over a Wi-Fi
53  * network, a game registered on device A can be discovered by a game on device
54  * B. Another example use case is an application discovering printers on the network.
55  *
56  * <p> The API currently supports DNS based service discovery and discovery is currently
57  * limited to a local network over Multicast DNS. DNS service discovery is described at
58  * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
59  *
60  * <p> The API is asynchronous, and responses to requests from an application are on listener
61  * callbacks on a separate internal thread.
62  *
63  * <p> There are three main operations the API supports - registration, discovery and resolution.
64  * <pre>
65  *                          Application start
66  *                                 |
67  *                                 |
68  *                                 |                  onServiceRegistered()
69  *                     Register any local services  /
70  *                      to be advertised with       \
71  *                       registerService()            onRegistrationFailed()
72  *                                 |
73  *                                 |
74  *                          discoverServices()
75  *                                 |
76  *                      Maintain a list to track
77  *                        discovered services
78  *                                 |
79  *                                 |--------->
80  *                                 |          |
81  *                                 |      onServiceFound()
82  *                                 |          |
83  *                                 |     add service to list
84  *                                 |          |
85  *                                 |<----------
86  *                                 |
87  *                                 |--------->
88  *                                 |          |
89  *                                 |      onServiceLost()
90  *                                 |          |
91  *                                 |   remove service from list
92  *                                 |          |
93  *                                 |<----------
94  *                                 |
95  *                                 |
96  *                                 | Connect to a service
97  *                                 | from list ?
98  *                                 |
99  *                          resolveService()
100  *                                 |
101  *                         onServiceResolved()
102  *                                 |
103  *                     Establish connection to service
104  *                     with the host and port information
105  *
106  * </pre>
107  * An application that needs to advertise itself over a network for other applications to
108  * discover it can do so with a call to {@link #registerService}. If Example is a http based
109  * application that can provide HTML data to peer services, it can register a name "Example"
110  * with service type "_http._tcp". A successful registration is notified with a callback to
111  * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified
112  * over {@link RegistrationListener#onRegistrationFailed}
113  *
114  * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
115  * with a call to {@link #discoverServices}. A service found is notified with a callback
116  * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
117  * {@link DiscoveryListener#onServiceLost}.
118  *
119  * <p> Once the peer application discovers the "Example" http service, and either needs to read the
120  * attributes of the service or wants to receive data from the "Example" application, it can
121  * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port
122  * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a
123  * failure is notified on {@link ResolveListener#onResolveFailed}.
124  *
125  * Applications can reserve for a service type at
126  * http://www.iana.org/form/ports-service. Existing services can be found at
127  * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
128  *
129  * {@see NsdServiceInfo}
130  */
131 @SystemService(Context.NSD_SERVICE)
132 public final class NsdManager {
133     private static final String TAG = NsdManager.class.getSimpleName();
134     private static final boolean DBG = false;
135 
136     /**
137      * When enabled, apps targeting < Android 12 are considered legacy for
138      * the NSD native daemon.
139      * The platform will only keep the daemon running as long as there are
140      * any legacy apps connected.
141      *
142      * After Android 12, directly communicate with native daemon might not
143      * work since the native damon won't always stay alive.
144      * Use the NSD APIs from NsdManager as the replacement is recommended.
145      * An another alternative could be bundling your own mdns solutions instead of
146      * depending on the system mdns native daemon.
147      *
148      * @hide
149      */
150     @ChangeId
151     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
152     public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
153 
154     /**
155      * Broadcast intent action to indicate whether network service discovery is
156      * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
157      * information as int.
158      *
159      * @see #EXTRA_NSD_STATE
160      */
161     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
162     public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
163 
164     /**
165      * The lookup key for an int that indicates whether network service discovery is enabled
166      * or disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
167      *
168      * @see #NSD_STATE_DISABLED
169      * @see #NSD_STATE_ENABLED
170      */
171     public static final String EXTRA_NSD_STATE = "nsd_state";
172 
173     /**
174      * Network service discovery is disabled
175      *
176      * @see #ACTION_NSD_STATE_CHANGED
177      */
178     public static final int NSD_STATE_DISABLED = 1;
179 
180     /**
181      * Network service discovery is enabled
182      *
183      * @see #ACTION_NSD_STATE_CHANGED
184      */
185     public static final int NSD_STATE_ENABLED = 2;
186 
187     /** @hide */
188     public static final int DISCOVER_SERVICES                       = 1;
189     /** @hide */
190     public static final int DISCOVER_SERVICES_STARTED               = 2;
191     /** @hide */
192     public static final int DISCOVER_SERVICES_FAILED                = 3;
193     /** @hide */
194     public static final int SERVICE_FOUND                           = 4;
195     /** @hide */
196     public static final int SERVICE_LOST                            = 5;
197 
198     /** @hide */
199     public static final int STOP_DISCOVERY                          = 6;
200     /** @hide */
201     public static final int STOP_DISCOVERY_FAILED                   = 7;
202     /** @hide */
203     public static final int STOP_DISCOVERY_SUCCEEDED                = 8;
204 
205     /** @hide */
206     public static final int REGISTER_SERVICE                        = 9;
207     /** @hide */
208     public static final int REGISTER_SERVICE_FAILED                 = 10;
209     /** @hide */
210     public static final int REGISTER_SERVICE_SUCCEEDED              = 11;
211 
212     /** @hide */
213     public static final int UNREGISTER_SERVICE                      = 12;
214     /** @hide */
215     public static final int UNREGISTER_SERVICE_FAILED               = 13;
216     /** @hide */
217     public static final int UNREGISTER_SERVICE_SUCCEEDED            = 14;
218 
219     /** @hide */
220     public static final int RESOLVE_SERVICE                         = 15;
221     /** @hide */
222     public static final int RESOLVE_SERVICE_FAILED                  = 16;
223     /** @hide */
224     public static final int RESOLVE_SERVICE_SUCCEEDED               = 17;
225 
226     /** @hide */
227     public static final int DAEMON_CLEANUP                          = 18;
228 
229     /** @hide */
230     public static final int DAEMON_STARTUP                          = 19;
231 
232     /** @hide */
233     public static final int ENABLE                                  = 20;
234     /** @hide */
235     public static final int DISABLE                                 = 21;
236 
237     /** @hide */
238     public static final int MDNS_SERVICE_EVENT                      = 22;
239 
240     /** @hide */
241     public static final int REGISTER_CLIENT                         = 23;
242     /** @hide */
243     public static final int UNREGISTER_CLIENT                       = 24;
244 
245     /** Dns based service discovery protocol */
246     public static final int PROTOCOL_DNS_SD = 0x0001;
247 
248     private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
249     static {
EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES")250         EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED")251         EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED");
EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED")252         EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED");
EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND")253         EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST")254         EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY")255         EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY");
EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED")256         EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED");
EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED")257         EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED");
EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE")258         EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE");
EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED")259         EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED");
EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED")260         EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED");
EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE")261         EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE");
EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED")262         EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED");
EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED")263         EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED");
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE")264         EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED")265         EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED")266         EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP")267         EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP")268         EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
EVENT_NAMES.put(ENABLE, "ENABLE")269         EVENT_NAMES.put(ENABLE, "ENABLE");
EVENT_NAMES.put(DISABLE, "DISABLE")270         EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT")271         EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
272     }
273 
274     /** @hide */
nameOf(int event)275     public static String nameOf(int event) {
276         String name = EVENT_NAMES.get(event);
277         if (name == null) {
278             return Integer.toString(event);
279         }
280         return name;
281     }
282 
283     private static final int FIRST_LISTENER_KEY = 1;
284 
285     private final INsdServiceConnector mService;
286     private final Context mContext;
287 
288     private int mListenerKey = FIRST_LISTENER_KEY;
289     @GuardedBy("mMapLock")
290     private final SparseArray mListenerMap = new SparseArray();
291     @GuardedBy("mMapLock")
292     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
293     @GuardedBy("mMapLock")
294     private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
295     private final Object mMapLock = new Object();
296     // Map of listener key sent by client -> per-network discovery tracker
297     @GuardedBy("mPerNetworkDiscoveryMap")
298     private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
299             mPerNetworkDiscoveryMap = new ArrayMap<>();
300 
301     private final ServiceHandler mHandler;
302 
303     private class PerNetworkDiscoveryTracker {
304         final String mServiceType;
305         final int mProtocolType;
306         final DiscoveryListener mBaseListener;
307         final Executor mBaseExecutor;
308         final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
309                 new ArrayMap<>();
310 
311         final NetworkCallback mNetworkCb = new NetworkCallback() {
312             @Override
313             public void onAvailable(@NonNull Network network) {
314                 final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
315                         network, mBaseListener, mBaseExecutor);
316                 mPerNetworkListeners.put(network, wrappedListener);
317                 // Run discovery callbacks inline on the service handler thread, which is the
318                 // same thread used by this NetworkCallback, but DelegatingDiscoveryListener will
319                 // use the base executor to run the wrapped callbacks.
320                 discoverServices(mServiceType, mProtocolType, network, Runnable::run,
321                         wrappedListener);
322             }
323 
324             @Override
325             public void onLost(@NonNull Network network) {
326                 final DelegatingDiscoveryListener listener = mPerNetworkListeners.get(network);
327                 if (listener == null) return;
328                 listener.notifyAllServicesLost();
329                 // Listener will be removed from map in discovery stopped callback
330                 stopServiceDiscovery(listener);
331             }
332         };
333 
334         // Accessed from mHandler
335         private boolean mStopRequested;
336 
start(@onNull NetworkRequest request)337         public void start(@NonNull NetworkRequest request) {
338             final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
339             cm.registerNetworkCallback(request, mNetworkCb, mHandler);
340             mHandler.post(() -> mBaseExecutor.execute(() ->
341                     mBaseListener.onDiscoveryStarted(mServiceType)));
342         }
343 
344         /**
345          * Stop discovery on all networks tracked by this class.
346          *
347          * This will request all underlying listeners to stop, and the last one to stop will call
348          * onDiscoveryStopped or onStopDiscoveryFailed.
349          *
350          * Must be called on the handler thread.
351          */
requestStop()352         public void requestStop() {
353             mHandler.post(() -> {
354                 mStopRequested = true;
355                 final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
356                 cm.unregisterNetworkCallback(mNetworkCb);
357                 if (mPerNetworkListeners.size() == 0) {
358                     mBaseExecutor.execute(() -> mBaseListener.onDiscoveryStopped(mServiceType));
359                     return;
360                 }
361                 for (int i = 0; i < mPerNetworkListeners.size(); i++) {
362                     final DelegatingDiscoveryListener listener = mPerNetworkListeners.valueAt(i);
363                     stopServiceDiscovery(listener);
364                 }
365             });
366         }
367 
PerNetworkDiscoveryTracker(String serviceType, int protocolType, Executor baseExecutor, DiscoveryListener baseListener)368         private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
369                 Executor baseExecutor, DiscoveryListener baseListener) {
370             mServiceType = serviceType;
371             mProtocolType = protocolType;
372             mBaseExecutor = baseExecutor;
373             mBaseListener = baseListener;
374         }
375 
376         /**
377          * Subset of NsdServiceInfo that is tracked to generate service lost notifications when a
378          * network is lost.
379          *
380          * Service lost notifications only contain service name, type and network, so only track
381          * that information (Network is known from the listener). This also implements
382          * equals/hashCode for usage in maps.
383          */
384         private class TrackedNsdInfo {
385             private final String mServiceName;
386             private final String mServiceType;
TrackedNsdInfo(NsdServiceInfo info)387             TrackedNsdInfo(NsdServiceInfo info) {
388                 mServiceName = info.getServiceName();
389                 mServiceType = info.getServiceType();
390             }
391 
392             @Override
hashCode()393             public int hashCode() {
394                 return Objects.hash(mServiceName, mServiceType);
395             }
396 
397             @Override
equals(Object obj)398             public boolean equals(Object obj) {
399                 if (!(obj instanceof TrackedNsdInfo)) return false;
400                 final TrackedNsdInfo other = (TrackedNsdInfo) obj;
401                 return Objects.equals(mServiceName, other.mServiceName)
402                         && Objects.equals(mServiceType, other.mServiceType);
403             }
404         }
405 
406         /**
407          * A listener wrapping calls to an app-provided listener, while keeping track of found
408          * services, so they can all be reported lost when the underlying network is lost.
409          *
410          * This should be registered to run on the service handler.
411          */
412         private class DelegatingDiscoveryListener implements DiscoveryListener {
413             private final Network mNetwork;
414             private final DiscoveryListener mWrapped;
415             private final Executor mWrappedExecutor;
416             private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
417 
DelegatingDiscoveryListener(Network network, DiscoveryListener listener, Executor executor)418             private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
419                     Executor executor) {
420                 mNetwork = network;
421                 mWrapped = listener;
422                 mWrappedExecutor = executor;
423             }
424 
notifyAllServicesLost()425             void notifyAllServicesLost() {
426                 for (int i = 0; i < mFoundInfo.size(); i++) {
427                     final TrackedNsdInfo trackedInfo = mFoundInfo.valueAt(i);
428                     final NsdServiceInfo serviceInfo = new NsdServiceInfo(
429                             trackedInfo.mServiceName, trackedInfo.mServiceType);
430                     serviceInfo.setNetwork(mNetwork);
431                     mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
432                 }
433             }
434 
435             @Override
onStartDiscoveryFailed(String serviceType, int errorCode)436             public void onStartDiscoveryFailed(String serviceType, int errorCode) {
437                 // The delegated listener is used when NsdManager takes care of starting/stopping
438                 // discovery on multiple networks. Failure to start on one network is not a global
439                 // failure to be reported up, as other networks may succeed: just log.
440                 Log.e(TAG, "Failed to start discovery for " + serviceType + " on " + mNetwork
441                         + " with code " + errorCode);
442                 mPerNetworkListeners.remove(mNetwork);
443             }
444 
445             @Override
onDiscoveryStarted(String serviceType)446             public void onDiscoveryStarted(String serviceType) {
447                 // Wrapped listener was called upon registration, it is not called for discovery
448                 // on each network
449             }
450 
451             @Override
onStopDiscoveryFailed(String serviceType, int errorCode)452             public void onStopDiscoveryFailed(String serviceType, int errorCode) {
453                 Log.e(TAG, "Failed to stop discovery for " + serviceType + " on " + mNetwork
454                         + " with code " + errorCode);
455                 mPerNetworkListeners.remove(mNetwork);
456                 if (mStopRequested && mPerNetworkListeners.size() == 0) {
457                     // Do not report onStopDiscoveryFailed when some underlying listeners failed:
458                     // this does not mean that all listeners did, and onStopDiscoveryFailed is not
459                     // actionable anyway. Just report that discovery stopped.
460                     mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
461                 }
462             }
463 
464             @Override
onDiscoveryStopped(String serviceType)465             public void onDiscoveryStopped(String serviceType) {
466                 mPerNetworkListeners.remove(mNetwork);
467                 if (mStopRequested && mPerNetworkListeners.size() == 0) {
468                     mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
469                 }
470             }
471 
472             @Override
onServiceFound(NsdServiceInfo serviceInfo)473             public void onServiceFound(NsdServiceInfo serviceInfo) {
474                 mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
475                 mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
476             }
477 
478             @Override
onServiceLost(NsdServiceInfo serviceInfo)479             public void onServiceLost(NsdServiceInfo serviceInfo) {
480                 mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
481                 mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
482             }
483         }
484     }
485 
486     /**
487      * Create a new Nsd instance. Applications use
488      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
489      * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}.
490      * @param service the Binder interface
491      * @hide - hide this because it takes in a parameter of type INsdManager, which
492      * is a system private class.
493      */
NsdManager(Context context, INsdManager service)494     public NsdManager(Context context, INsdManager service) {
495         mContext = context;
496 
497         HandlerThread t = new HandlerThread("NsdManager");
498         t.start();
499         mHandler = new ServiceHandler(t.getLooper());
500 
501         try {
502             mService = service.connect(new NsdCallbackImpl(mHandler));
503         } catch (RemoteException e) {
504             throw new RuntimeException("Failed to connect to NsdService");
505         }
506 
507         // Only proactively start the daemon if the target SDK < S, otherwise the internal service
508         // would automatically start/stop the native daemon as needed.
509         if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
510             try {
511                 mService.startDaemon();
512             } catch (RemoteException e) {
513                 Log.e(TAG, "Failed to proactively start daemon");
514                 // Continue: the daemon can still be started on-demand later
515             }
516         }
517     }
518 
519     private static class NsdCallbackImpl extends INsdManagerCallback.Stub {
520         private final Handler mServHandler;
521 
NsdCallbackImpl(Handler serviceHandler)522         NsdCallbackImpl(Handler serviceHandler) {
523             mServHandler = serviceHandler;
524         }
525 
sendInfo(int message, int listenerKey, NsdServiceInfo info)526         private void sendInfo(int message, int listenerKey, NsdServiceInfo info) {
527             mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info));
528         }
529 
sendError(int message, int listenerKey, int error)530         private void sendError(int message, int listenerKey, int error) {
531             mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey));
532         }
533 
sendNoArg(int message, int listenerKey)534         private void sendNoArg(int message, int listenerKey) {
535             mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey));
536         }
537 
538         @Override
onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info)539         public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
540             sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info);
541         }
542 
543         @Override
onDiscoverServicesFailed(int listenerKey, int error)544         public void onDiscoverServicesFailed(int listenerKey, int error) {
545             sendError(DISCOVER_SERVICES_FAILED, listenerKey, error);
546         }
547 
548         @Override
onServiceFound(int listenerKey, NsdServiceInfo info)549         public void onServiceFound(int listenerKey, NsdServiceInfo info) {
550             sendInfo(SERVICE_FOUND, listenerKey, info);
551         }
552 
553         @Override
onServiceLost(int listenerKey, NsdServiceInfo info)554         public void onServiceLost(int listenerKey, NsdServiceInfo info) {
555             sendInfo(SERVICE_LOST, listenerKey, info);
556         }
557 
558         @Override
onStopDiscoveryFailed(int listenerKey, int error)559         public void onStopDiscoveryFailed(int listenerKey, int error) {
560             sendError(STOP_DISCOVERY_FAILED, listenerKey, error);
561         }
562 
563         @Override
onStopDiscoverySucceeded(int listenerKey)564         public void onStopDiscoverySucceeded(int listenerKey) {
565             sendNoArg(STOP_DISCOVERY_SUCCEEDED, listenerKey);
566         }
567 
568         @Override
onRegisterServiceFailed(int listenerKey, int error)569         public void onRegisterServiceFailed(int listenerKey, int error) {
570             sendError(REGISTER_SERVICE_FAILED, listenerKey, error);
571         }
572 
573         @Override
onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info)574         public void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
575             sendInfo(REGISTER_SERVICE_SUCCEEDED, listenerKey, info);
576         }
577 
578         @Override
onUnregisterServiceFailed(int listenerKey, int error)579         public void onUnregisterServiceFailed(int listenerKey, int error) {
580             sendError(UNREGISTER_SERVICE_FAILED, listenerKey, error);
581         }
582 
583         @Override
onUnregisterServiceSucceeded(int listenerKey)584         public void onUnregisterServiceSucceeded(int listenerKey) {
585             sendNoArg(UNREGISTER_SERVICE_SUCCEEDED, listenerKey);
586         }
587 
588         @Override
onResolveServiceFailed(int listenerKey, int error)589         public void onResolveServiceFailed(int listenerKey, int error) {
590             sendError(RESOLVE_SERVICE_FAILED, listenerKey, error);
591         }
592 
593         @Override
onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info)594         public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
595             sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info);
596         }
597     }
598 
599     /**
600      * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
601      * {@link RegistrationListener#onUnregistrationFailed},
602      * {@link DiscoveryListener#onStartDiscoveryFailed},
603      * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}.
604      *
605      * Indicates that the operation failed due to an internal error.
606      */
607     public static final int FAILURE_INTERNAL_ERROR               = 0;
608 
609     /**
610      * Indicates that the operation failed because it is already active.
611      */
612     public static final int FAILURE_ALREADY_ACTIVE              = 3;
613 
614     /**
615      * Indicates that the operation failed because the maximum outstanding
616      * requests from the applications have reached.
617      */
618     public static final int FAILURE_MAX_LIMIT                   = 4;
619 
620     /** Interface for callback invocation for service discovery */
621     public interface DiscoveryListener {
622 
onStartDiscoveryFailed(String serviceType, int errorCode)623         public void onStartDiscoveryFailed(String serviceType, int errorCode);
624 
onStopDiscoveryFailed(String serviceType, int errorCode)625         public void onStopDiscoveryFailed(String serviceType, int errorCode);
626 
onDiscoveryStarted(String serviceType)627         public void onDiscoveryStarted(String serviceType);
628 
onDiscoveryStopped(String serviceType)629         public void onDiscoveryStopped(String serviceType);
630 
onServiceFound(NsdServiceInfo serviceInfo)631         public void onServiceFound(NsdServiceInfo serviceInfo);
632 
onServiceLost(NsdServiceInfo serviceInfo)633         public void onServiceLost(NsdServiceInfo serviceInfo);
634     }
635 
636     /** Interface for callback invocation for service registration */
637     public interface RegistrationListener {
638 
onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode)639         public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
640 
onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode)641         public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
642 
onServiceRegistered(NsdServiceInfo serviceInfo)643         public void onServiceRegistered(NsdServiceInfo serviceInfo);
644 
onServiceUnregistered(NsdServiceInfo serviceInfo)645         public void onServiceUnregistered(NsdServiceInfo serviceInfo);
646     }
647 
648     /** Interface for callback invocation for service resolution */
649     public interface ResolveListener {
650 
onResolveFailed(NsdServiceInfo serviceInfo, int errorCode)651         public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
652 
onServiceResolved(NsdServiceInfo serviceInfo)653         public void onServiceResolved(NsdServiceInfo serviceInfo);
654     }
655 
656     @VisibleForTesting
657     class ServiceHandler extends Handler {
ServiceHandler(Looper looper)658         ServiceHandler(Looper looper) {
659             super(looper);
660         }
661 
662         @Override
handleMessage(Message message)663         public void handleMessage(Message message) {
664             // Do not use message in the executor lambdas, as it will be recycled once this method
665             // returns. Keep references to its content instead.
666             final int what = message.what;
667             final int errorCode = message.arg1;
668             final int key = message.arg2;
669             final Object obj = message.obj;
670             final Object listener;
671             final NsdServiceInfo ns;
672             final Executor executor;
673             synchronized (mMapLock) {
674                 listener = mListenerMap.get(key);
675                 ns = mServiceMap.get(key);
676                 executor = mExecutorMap.get(key);
677             }
678             if (listener == null) {
679                 Log.d(TAG, "Stale key " + key);
680                 return;
681             }
682             if (DBG) {
683                 Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
684             }
685             switch (what) {
686                 case DISCOVER_SERVICES_STARTED:
687                     final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
688                     executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
689                     break;
690                 case DISCOVER_SERVICES_FAILED:
691                     removeListener(key);
692                     executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
693                             getNsdServiceInfoType(ns), errorCode));
694                     break;
695                 case SERVICE_FOUND:
696                     executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
697                             (NsdServiceInfo) obj));
698                     break;
699                 case SERVICE_LOST:
700                     executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
701                             (NsdServiceInfo) obj));
702                     break;
703                 case STOP_DISCOVERY_FAILED:
704                     // TODO: failure to stop discovery should be internal and retried internally, as
705                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
706                     removeListener(key);
707                     executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
708                             getNsdServiceInfoType(ns), errorCode));
709                     break;
710                 case STOP_DISCOVERY_SUCCEEDED:
711                     removeListener(key);
712                     executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
713                             getNsdServiceInfoType(ns)));
714                     break;
715                 case REGISTER_SERVICE_FAILED:
716                     removeListener(key);
717                     executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
718                             ns, errorCode));
719                     break;
720                 case REGISTER_SERVICE_SUCCEEDED:
721                     executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
722                             (NsdServiceInfo) obj));
723                     break;
724                 case UNREGISTER_SERVICE_FAILED:
725                     removeListener(key);
726                     executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
727                             ns, errorCode));
728                     break;
729                 case UNREGISTER_SERVICE_SUCCEEDED:
730                     // TODO: do not unregister listener until service is unregistered, or provide
731                     // alternative way for unregistering ?
732                     removeListener(key);
733                     executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
734                             ns));
735                     break;
736                 case RESOLVE_SERVICE_FAILED:
737                     removeListener(key);
738                     executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
739                             ns, errorCode));
740                     break;
741                 case RESOLVE_SERVICE_SUCCEEDED:
742                     removeListener(key);
743                     executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
744                             (NsdServiceInfo) obj));
745                     break;
746                 default:
747                     Log.d(TAG, "Ignored " + message);
748                     break;
749             }
750         }
751     }
752 
nextListenerKey()753     private int nextListenerKey() {
754         // Ensure mListenerKey >= FIRST_LISTENER_KEY;
755         mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
756         return mListenerKey;
757     }
758 
759     // Assert that the listener is not in the map, then add it and returns its key
putListener(Object listener, Executor e, NsdServiceInfo s)760     private int putListener(Object listener, Executor e, NsdServiceInfo s) {
761         checkListener(listener);
762         final int key;
763         synchronized (mMapLock) {
764             int valueIndex = mListenerMap.indexOfValue(listener);
765             if (valueIndex != -1) {
766                 throw new IllegalArgumentException("listener already in use");
767             }
768             key = nextListenerKey();
769             mListenerMap.put(key, listener);
770             mServiceMap.put(key, s);
771             mExecutorMap.put(key, e);
772         }
773         return key;
774     }
775 
removeListener(int key)776     private void removeListener(int key) {
777         synchronized (mMapLock) {
778             mListenerMap.remove(key);
779             mServiceMap.remove(key);
780             mExecutorMap.remove(key);
781         }
782     }
783 
getListenerKey(Object listener)784     private int getListenerKey(Object listener) {
785         checkListener(listener);
786         synchronized (mMapLock) {
787             int valueIndex = mListenerMap.indexOfValue(listener);
788             if (valueIndex == -1) {
789                 throw new IllegalArgumentException("listener not registered");
790             }
791             return mListenerMap.keyAt(valueIndex);
792         }
793     }
794 
getNsdServiceInfoType(NsdServiceInfo s)795     private static String getNsdServiceInfoType(NsdServiceInfo s) {
796         if (s == null) return "?";
797         return s.getServiceType();
798     }
799 
800     /**
801      * Register a service to be discovered by other services.
802      *
803      * <p> The function call immediately returns after sending a request to register service
804      * to the framework. The application is notified of a successful registration
805      * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
806      * through {@link RegistrationListener#onRegistrationFailed}.
807      *
808      * <p> The application should call {@link #unregisterService} when the service
809      * registration is no longer required, and/or whenever the application is stopped.
810      *
811      * @param serviceInfo The service being registered
812      * @param protocolType The service discovery protocol
813      * @param listener The listener notifies of a successful registration and is used to
814      * unregister this service through a call on {@link #unregisterService}. Cannot be null.
815      * Cannot be in use for an active service registration.
816      */
registerService(NsdServiceInfo serviceInfo, int protocolType, RegistrationListener listener)817     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
818             RegistrationListener listener) {
819         registerService(serviceInfo, protocolType, Runnable::run, listener);
820     }
821 
822     /**
823      * Register a service to be discovered by other services.
824      *
825      * <p> The function call immediately returns after sending a request to register service
826      * to the framework. The application is notified of a successful registration
827      * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
828      * through {@link RegistrationListener#onRegistrationFailed}.
829      *
830      * <p> The application should call {@link #unregisterService} when the service
831      * registration is no longer required, and/or whenever the application is stopped.
832      * @param serviceInfo The service being registered
833      * @param protocolType The service discovery protocol
834      * @param executor Executor to run listener callbacks with
835      * @param listener The listener notifies of a successful registration and is used to
836      * unregister this service through a call on {@link #unregisterService}. Cannot be null.
837      */
registerService(@onNull NsdServiceInfo serviceInfo, int protocolType, @NonNull Executor executor, @NonNull RegistrationListener listener)838     public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
839             @NonNull Executor executor, @NonNull RegistrationListener listener) {
840         if (serviceInfo.getPort() <= 0) {
841             throw new IllegalArgumentException("Invalid port number");
842         }
843         checkServiceInfo(serviceInfo);
844         checkProtocol(protocolType);
845         int key = putListener(listener, executor, serviceInfo);
846         try {
847             mService.registerService(key, serviceInfo);
848         } catch (RemoteException e) {
849             e.rethrowFromSystemServer();
850         }
851     }
852 
853     /**
854      * Unregister a service registered through {@link #registerService}. A successful
855      * unregister is notified to the application with a call to
856      * {@link RegistrationListener#onServiceUnregistered}.
857      *
858      * @param listener This should be the listener object that was passed to
859      * {@link #registerService}. It identifies the service that should be unregistered
860      * and notifies of a successful or unsuccessful unregistration via the listener
861      * callbacks.  In API versions 20 and above, the listener object may be used for
862      * another service registration once the callback has been called.  In API versions <= 19,
863      * there is no entirely reliable way to know when a listener may be re-used, and a new
864      * listener should be created for each service registration request.
865      */
unregisterService(RegistrationListener listener)866     public void unregisterService(RegistrationListener listener) {
867         int id = getListenerKey(listener);
868         try {
869             mService.unregisterService(id);
870         } catch (RemoteException e) {
871             e.rethrowFromSystemServer();
872         }
873     }
874 
875     /**
876      * Initiate service discovery to browse for instances of a service type. Service discovery
877      * consumes network bandwidth and will continue until the application calls
878      * {@link #stopServiceDiscovery}.
879      *
880      * <p> The function call immediately returns after sending a request to start service
881      * discovery to the framework. The application is notified of a success to initiate
882      * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
883      * through {@link DiscoveryListener#onStartDiscoveryFailed}.
884      *
885      * <p> Upon successful start, application is notified when a service is found with
886      * {@link DiscoveryListener#onServiceFound} or when a service is lost with
887      * {@link DiscoveryListener#onServiceLost}.
888      *
889      * <p> Upon failure to start, service discovery is not active and application does
890      * not need to invoke {@link #stopServiceDiscovery}
891      *
892      * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
893      * service type is no longer required, and/or whenever the application is paused or
894      * stopped.
895      *
896      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
897      * http services or "_ipp._tcp" for printers
898      * @param protocolType The service discovery protocol
899      * @param listener  The listener notifies of a successful discovery and is used
900      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
901      * Cannot be null. Cannot be in use for an active service discovery.
902      */
discoverServices(String serviceType, int protocolType, DiscoveryListener listener)903     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
904         discoverServices(serviceType, protocolType, (Network) null, Runnable::run, listener);
905     }
906 
907     /**
908      * Initiate service discovery to browse for instances of a service type. Service discovery
909      * consumes network bandwidth and will continue until the application calls
910      * {@link #stopServiceDiscovery}.
911      *
912      * <p> The function call immediately returns after sending a request to start service
913      * discovery to the framework. The application is notified of a success to initiate
914      * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
915      * through {@link DiscoveryListener#onStartDiscoveryFailed}.
916      *
917      * <p> Upon successful start, application is notified when a service is found with
918      * {@link DiscoveryListener#onServiceFound} or when a service is lost with
919      * {@link DiscoveryListener#onServiceLost}.
920      *
921      * <p> Upon failure to start, service discovery is not active and application does
922      * not need to invoke {@link #stopServiceDiscovery}
923      *
924      * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
925      * service type is no longer required, and/or whenever the application is paused or
926      * stopped.
927      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
928      * http services or "_ipp._tcp" for printers
929      * @param protocolType The service discovery protocol
930      * @param network Network to discover services on, or null to discover on all available networks
931      * @param executor Executor to run listener callbacks with
932      * @param listener  The listener notifies of a successful discovery and is used
933      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
934      */
discoverServices(@onNull String serviceType, int protocolType, @Nullable Network network, @NonNull Executor executor, @NonNull DiscoveryListener listener)935     public void discoverServices(@NonNull String serviceType, int protocolType,
936             @Nullable Network network, @NonNull Executor executor,
937             @NonNull DiscoveryListener listener) {
938         if (TextUtils.isEmpty(serviceType)) {
939             throw new IllegalArgumentException("Service type cannot be empty");
940         }
941         checkProtocol(protocolType);
942 
943         NsdServiceInfo s = new NsdServiceInfo();
944         s.setServiceType(serviceType);
945         s.setNetwork(network);
946 
947         int key = putListener(listener, executor, s);
948         try {
949             mService.discoverServices(key, s);
950         } catch (RemoteException e) {
951             e.rethrowFromSystemServer();
952         }
953     }
954 
955     /**
956      * Initiate service discovery to browse for instances of a service type. Service discovery
957      * consumes network bandwidth and will continue until the application calls
958      * {@link #stopServiceDiscovery}.
959      *
960      * <p> The function call immediately returns after sending a request to start service
961      * discovery to the framework. The application is notified of a success to initiate
962      * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
963      * through {@link DiscoveryListener#onStartDiscoveryFailed}.
964      *
965      * <p> Upon successful start, application is notified when a service is found with
966      * {@link DiscoveryListener#onServiceFound} or when a service is lost with
967      * {@link DiscoveryListener#onServiceLost}.
968      *
969      * <p> Upon failure to start, service discovery is not active and application does
970      * not need to invoke {@link #stopServiceDiscovery}
971      *
972      * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
973      * service type is no longer required, and/or whenever the application is paused or
974      * stopped.
975      *
976      * <p> During discovery, new networks may connect or existing networks may disconnect - for
977      * example if wifi is reconnected. When a service was found on a network that disconnects,
978      * {@link DiscoveryListener#onServiceLost} will be called. If a new network connects that
979      * matches the {@link NetworkRequest}, {@link DiscoveryListener#onServiceFound} will be called
980      * for services found on that network. Applications that do not want to track networks
981      * themselves are encouraged to use this method instead of other overloads of
982      * {@code discoverServices}, as they will receive proper notifications when a service becomes
983      * available or unavailable due to network changes.
984      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
985      * http services or "_ipp._tcp" for printers
986      * @param protocolType The service discovery protocol
987      * @param networkRequest Request specifying networks that should be considered when discovering
988      * @param executor Executor to run listener callbacks with
989      * @param listener  The listener notifies of a successful discovery and is used
990      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
991      */
992     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
discoverServices(@onNull String serviceType, int protocolType, @NonNull NetworkRequest networkRequest, @NonNull Executor executor, @NonNull DiscoveryListener listener)993     public void discoverServices(@NonNull String serviceType, int protocolType,
994             @NonNull NetworkRequest networkRequest, @NonNull Executor executor,
995             @NonNull DiscoveryListener listener) {
996         if (TextUtils.isEmpty(serviceType)) {
997             throw new IllegalArgumentException("Service type cannot be empty");
998         }
999         Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
1000         checkProtocol(protocolType);
1001 
1002         NsdServiceInfo s = new NsdServiceInfo();
1003         s.setServiceType(serviceType);
1004 
1005         final int baseListenerKey = putListener(listener, executor, s);
1006 
1007         final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
1008                 serviceType, protocolType, executor, listener);
1009 
1010         synchronized (mPerNetworkDiscoveryMap) {
1011             mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
1012             discoveryInfo.start(networkRequest);
1013         }
1014     }
1015 
1016     /**
1017      * Stop service discovery initiated with {@link #discoverServices}.  An active service
1018      * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
1019      * and it stays active until the application invokes a stop service discovery. A successful
1020      * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
1021      *
1022      * <p> Upon failure to stop service discovery, application is notified through
1023      * {@link DiscoveryListener#onStopDiscoveryFailed}.
1024      *
1025      * @param listener This should be the listener object that was passed to {@link #discoverServices}.
1026      * It identifies the discovery that should be stopped and notifies of a successful or
1027      * unsuccessful stop.  In API versions 20 and above, the listener object may be used for
1028      * another service discovery once the callback has been called.  In API versions <= 19,
1029      * there is no entirely reliable way to know when a listener may be re-used, and a new
1030      * listener should be created for each service discovery request.
1031      */
stopServiceDiscovery(DiscoveryListener listener)1032     public void stopServiceDiscovery(DiscoveryListener listener) {
1033         int id = getListenerKey(listener);
1034         // If this is a PerNetworkDiscovery request, handle it as such
1035         synchronized (mPerNetworkDiscoveryMap) {
1036             final PerNetworkDiscoveryTracker info = mPerNetworkDiscoveryMap.get(id);
1037             if (info != null) {
1038                 info.requestStop();
1039                 return;
1040             }
1041         }
1042         try {
1043             mService.stopDiscovery(id);
1044         } catch (RemoteException e) {
1045             e.rethrowFromSystemServer();
1046         }
1047     }
1048 
1049     /**
1050      * Resolve a discovered service. An application can resolve a service right before
1051      * establishing a connection to fetch the IP and port details on which to setup
1052      * the connection.
1053      *
1054      * @param serviceInfo service to be resolved
1055      * @param listener to receive callback upon success or failure. Cannot be null.
1056      * Cannot be in use for an active service resolution.
1057      */
resolveService(NsdServiceInfo serviceInfo, ResolveListener listener)1058     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
1059         resolveService(serviceInfo, Runnable::run, listener);
1060     }
1061 
1062     /**
1063      * Resolve a discovered service. An application can resolve a service right before
1064      * establishing a connection to fetch the IP and port details on which to setup
1065      * the connection.
1066      * @param serviceInfo service to be resolved
1067      * @param executor Executor to run listener callbacks with
1068      * @param listener to receive callback upon success or failure.
1069      */
resolveService(@onNull NsdServiceInfo serviceInfo, @NonNull Executor executor, @NonNull ResolveListener listener)1070     public void resolveService(@NonNull NsdServiceInfo serviceInfo,
1071             @NonNull Executor executor, @NonNull ResolveListener listener) {
1072         checkServiceInfo(serviceInfo);
1073         int key = putListener(listener, executor, serviceInfo);
1074         try {
1075             mService.resolveService(key, serviceInfo);
1076         } catch (RemoteException e) {
1077             e.rethrowFromSystemServer();
1078         }
1079     }
1080 
checkListener(Object listener)1081     private static void checkListener(Object listener) {
1082         Objects.requireNonNull(listener, "listener cannot be null");
1083     }
1084 
checkProtocol(int protocolType)1085     private static void checkProtocol(int protocolType) {
1086         if (protocolType != PROTOCOL_DNS_SD) {
1087             throw new IllegalArgumentException("Unsupported protocol");
1088         }
1089     }
1090 
checkServiceInfo(NsdServiceInfo serviceInfo)1091     private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
1092         Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
1093         if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
1094             throw new IllegalArgumentException("Service name cannot be empty");
1095         }
1096         if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
1097             throw new IllegalArgumentException("Service type cannot be empty");
1098         }
1099     }
1100 }
1101