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