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