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