NsdManager.java revision 92784670c48759c0db604ddb95c05a7b9bdebed8
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 17package android.net.nsd; 18 19import android.content.Context; 20import android.os.Binder; 21import android.os.IBinder; 22import android.os.Handler; 23import android.os.Looper; 24import android.os.Message; 25import android.os.RemoteException; 26import android.os.Messenger; 27import android.text.TextUtils; 28import android.util.Log; 29 30import com.android.internal.util.AsyncChannel; 31import com.android.internal.util.Protocol; 32 33/** 34 * The Network Service Discovery Manager class provides the API to discover services 35 * on a network. As an example, if device A and device B are connected over a Wi-Fi 36 * network, a game registered on device A can be discovered by a game on device 37 * B. Another example use case is an application discovering printers on the network. 38 * 39 * <p> The API currently supports DNS based service discovery and discovery is currently 40 * limited to a local network over Multicast DNS. In future, it will be extended to 41 * support wide area discovery and other service discovery mechanisms. 42 * DNS service discovery is described at http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt 43 * 44 * <p> The API is asynchronous and responses to requests from an application are on listener 45 * callbacks provided by the application. The application must invoke {@link #initialize} before 46 * doing any other operation. 47 * 48 * <p> There are three main operations the API supports - registration, discovery and resolution. 49 * <pre> 50 * Application start 51 * | 52 * | <---------------------------------------------- 53 * initialize() | 54 * | | 55 * | Wait until channel connects | 56 * | before doing any operation | 57 * | | 58 * onChannelConnected() __________ | 59 * | | | 60 * | | | 61 * | onServiceRegistered() | | 62 * Register any local services / | | 63 * to be advertised with \ | | If application needs to 64 * registerService() onFailure() | | do any further operations 65 * | | | again, it needs to 66 * | | | initialize() connection 67 * discoverServices() | | to framework again 68 * | | | 69 * Maintain a list to track | | 70 * discovered services | | 71 * | | | 72 * |---------> |-> onChannelDisconnected() 73 * | | | 74 * | onServiceFound() | 75 * | | | 76 * | add service to list | 77 * | | | 78 * |<---------- | 79 * | | 80 * |---------> | 81 * | | | 82 * | onServiceLost() | 83 * | | | 84 * | remove service from list | 85 * | | | 86 * |<---------- | 87 * | | 88 * | | 89 * | Connect to a service | 90 * | from list ? | 91 * | | 92 * resolveService() | 93 * | | 94 * onServiceResolved() | 95 * | | 96 * Establish connection to service | 97 * with the host and port information | 98 * | | 99 * | ___________| 100 * deinitialize() 101 * when done with all operations 102 * or before quit 103 * 104 * </pre> 105 * An application that needs to advertise itself over a network for other applications to 106 * discover it can do so with a call to {@link #registerService}. If Example is a http based 107 * application that can provide HTML data to peer services, it can register a name "Example" 108 * with service type "_http._tcp". A successful registration is notified with a callback to 109 * {@link DnsSdRegisterListener#onServiceRegistered} and a failure to register is notified 110 * over {@link DnsSdRegisterListener#onFailure} 111 * 112 * <p> A peer application looking for http services can initiate a discovery for "_http._tcp" 113 * with a call to {@link #discoverServices}. A service found is notified with a callback 114 * to {@link DnsSdDiscoveryListener#onServiceFound} and a service lost is notified on 115 * {@link DnsSdDiscoveryListener#onServiceLost}. 116 * 117 * <p> Once the peer application discovers the "Example" http srevice, and needs to receive data 118 * from the "Example" application, it can initiate a resolve with {@link #resolveService} to 119 * resolve the host and port details for the purpose of establishing a connection. A successful 120 * resolve is notified on {@link DnsSdResolveListener#onServiceResolved} and a failure is notified 121 * on {@link DnsSdResolveListener#onFailure}. 122 * 123 * Applications can reserve for a service type at 124 * http://www.iana.org/form/ports-service. Existing services can be found at 125 * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml 126 * 127 * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) 128 * Context.getSystemService(Context.NSD_SERVICE)}. 129 * 130 * {@see DnsSdServiceInfo} 131 */ 132public class NsdManager { 133 private static final String TAG = "NsdManager"; 134 INsdManager mService; 135 136 private static final int BASE = Protocol.BASE_NSD_MANAGER; 137 138 /** @hide */ 139 public static final int DISCOVER_SERVICES = BASE + 1; 140 /** @hide */ 141 public static final int DISCOVER_SERVICES_STARTED = BASE + 2; 142 /** @hide */ 143 public static final int DISCOVER_SERVICES_FAILED = BASE + 3; 144 /** @hide */ 145 public static final int SERVICE_FOUND = BASE + 4; 146 /** @hide */ 147 public static final int SERVICE_LOST = BASE + 5; 148 149 /** @hide */ 150 public static final int STOP_DISCOVERY = BASE + 6; 151 /** @hide */ 152 public static final int STOP_DISCOVERY_FAILED = BASE + 7; 153 /** @hide */ 154 public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8; 155 156 /** @hide */ 157 public static final int REGISTER_SERVICE = BASE + 9; 158 /** @hide */ 159 public static final int REGISTER_SERVICE_FAILED = BASE + 10; 160 /** @hide */ 161 public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11; 162 163 /** @hide */ 164 public static final int UNREGISTER_SERVICE = BASE + 12; 165 /** @hide */ 166 public static final int UNREGISTER_SERVICE_FAILED = BASE + 13; 167 /** @hide */ 168 public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14; 169 170 /** @hide */ 171 public static final int UPDATE_SERVICE = BASE + 15; 172 /** @hide */ 173 public static final int UPDATE_SERVICE_FAILED = BASE + 16; 174 /** @hide */ 175 public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 17; 176 177 /** @hide */ 178 public static final int RESOLVE_SERVICE = BASE + 18; 179 /** @hide */ 180 public static final int RESOLVE_SERVICE_FAILED = BASE + 19; 181 /** @hide */ 182 public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20; 183 184 /** @hide */ 185 public static final int STOP_RESOLVE = BASE + 21; 186 /** @hide */ 187 public static final int STOP_RESOLVE_FAILED = BASE + 22; 188 /** @hide */ 189 public static final int STOP_RESOLVE_SUCCEEDED = BASE + 23; 190 191 /** 192 * Create a new Nsd instance. Applications use 193 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 194 * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}. 195 * @param service the Binder interface 196 * @hide - hide this because it takes in a parameter of type INsdManager, which 197 * is a system private class. 198 */ 199 public NsdManager(INsdManager service) { 200 mService = service; 201 } 202 203 /** 204 * Passed with onFailure() calls. 205 * Indicates that the operation failed due to an internal error. 206 */ 207 public static final int ERROR = 0; 208 209 /** 210 * Passed with onFailure() calls. 211 * Indicates that the operation failed because service discovery 212 * is unsupported on the device. 213 */ 214 public static final int UNSUPPORTED = 1; 215 216 /** 217 * Passed with onFailure() calls. 218 * Indicates that the operation failed because the framework is 219 * busy and unable to service the request. 220 */ 221 public static final int BUSY = 2; 222 223 /** 224 * Passed with onFailure() calls. 225 * Indicates that the operation failed because it is already active. 226 */ 227 public static final int ALREADY_ACTIVE = 3; 228 229 /** 230 * Passed with onFailure() calls. 231 * Indicates that the operation failed because maximum limit on 232 * service registrations has reached. 233 */ 234 public static final int MAX_REGS_REACHED = 4; 235 236 /** Interface for callback invocation when framework channel is connected or lost */ 237 public interface ChannelListener { 238 /** 239 * The channel to the framework is connected. 240 * Application can initiate calls into the framework using the channel instance passed. 241 */ 242 public void onChannelConnected(Channel c); 243 /** 244 * The channel to the framework has been disconnected. 245 * Application could try re-initializing using {@link #initialize} 246 */ 247 public void onChannelDisconnected(); 248 } 249 250 /** Generic interface for callback invocation for a success or failure */ 251 public interface ActionListener { 252 253 public void onFailure(int errorCode); 254 255 public void onSuccess(); 256 } 257 258 /** Interface for callback invocation for service discovery */ 259 public interface DnsSdDiscoveryListener { 260 261 public void onFailure(int errorCode); 262 263 public void onStarted(String serviceType); 264 265 public void onServiceFound(DnsSdServiceInfo serviceInfo); 266 267 public void onServiceLost(DnsSdServiceInfo serviceInfo); 268 269 } 270 271 /** Interface for callback invocation for service registration */ 272 public interface DnsSdRegisterListener { 273 274 public void onFailure(int errorCode); 275 276 public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); 277 } 278 279 /** @hide */ 280 public interface DnsSdUpdateRegistrationListener { 281 282 public void onFailure(int errorCode); 283 284 public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); 285 } 286 287 /** Interface for callback invocation for service resolution */ 288 public interface DnsSdResolveListener { 289 290 public void onFailure(int errorCode); 291 292 public void onServiceResolved(DnsSdServiceInfo serviceInfo); 293 } 294 295 /** 296 * A channel that connects the application to the NetworkService framework. 297 * Most service operations require a Channel as an argument. An instance of Channel is obtained 298 * by doing a call on {@link #initialize} 299 */ 300 public static class Channel { 301 Channel(Looper looper, ChannelListener l) { 302 mAsyncChannel = new AsyncChannel(); 303 mHandler = new ServiceHandler(looper); 304 mChannelListener = l; 305 } 306 private ChannelListener mChannelListener; 307 private DnsSdDiscoveryListener mDnsSdDiscoveryListener; 308 private ActionListener mDnsSdStopDiscoveryListener; 309 private DnsSdRegisterListener mDnsSdRegisterListener; 310 private ActionListener mDnsSdUnregisterListener; 311 private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; 312 private DnsSdResolveListener mDnsSdResolveListener; 313 private ActionListener mDnsSdStopResolveListener; 314 315 AsyncChannel mAsyncChannel; 316 ServiceHandler mHandler; 317 class ServiceHandler extends Handler { 318 ServiceHandler(Looper looper) { 319 super(looper); 320 } 321 322 @Override 323 public void handleMessage(Message message) { 324 switch (message.what) { 325 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 326 mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 327 break; 328 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 329 if (mChannelListener != null) { 330 mChannelListener.onChannelConnected(Channel.this); 331 } 332 break; 333 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 334 if (mChannelListener != null) { 335 mChannelListener.onChannelDisconnected(); 336 mChannelListener = null; 337 } 338 break; 339 case DISCOVER_SERVICES_STARTED: 340 if (mDnsSdDiscoveryListener != null) { 341 mDnsSdDiscoveryListener.onStarted((String) message.obj); 342 } 343 break; 344 case DISCOVER_SERVICES_FAILED: 345 if (mDnsSdDiscoveryListener != null) { 346 mDnsSdDiscoveryListener.onFailure(message.arg1); 347 } 348 break; 349 case SERVICE_FOUND: 350 if (mDnsSdDiscoveryListener != null) { 351 mDnsSdDiscoveryListener.onServiceFound( 352 (DnsSdServiceInfo) message.obj); 353 } 354 break; 355 case SERVICE_LOST: 356 if (mDnsSdDiscoveryListener != null) { 357 mDnsSdDiscoveryListener.onServiceLost( 358 (DnsSdServiceInfo) message.obj); 359 } 360 break; 361 case STOP_DISCOVERY_FAILED: 362 if (mDnsSdStopDiscoveryListener != null) { 363 mDnsSdStopDiscoveryListener.onFailure(message.arg1); 364 } 365 break; 366 case STOP_DISCOVERY_SUCCEEDED: 367 if (mDnsSdStopDiscoveryListener != null) { 368 mDnsSdStopDiscoveryListener.onSuccess(); 369 } 370 break; 371 case REGISTER_SERVICE_FAILED: 372 if (mDnsSdRegisterListener != null) { 373 mDnsSdRegisterListener.onFailure(message.arg1); 374 } 375 break; 376 case REGISTER_SERVICE_SUCCEEDED: 377 if (mDnsSdRegisterListener != null) { 378 mDnsSdRegisterListener.onServiceRegistered(message.arg1, 379 (DnsSdServiceInfo) message.obj); 380 } 381 break; 382 case UNREGISTER_SERVICE_FAILED: 383 if (mDnsSdUnregisterListener != null) { 384 mDnsSdUnregisterListener.onFailure(message.arg1); 385 } 386 break; 387 case UNREGISTER_SERVICE_SUCCEEDED: 388 if (mDnsSdUnregisterListener != null) { 389 mDnsSdUnregisterListener.onSuccess(); 390 } 391 break; 392 case UPDATE_SERVICE_FAILED: 393 if (mDnsSdUpdateListener != null) { 394 mDnsSdUpdateListener.onFailure(message.arg1); 395 } 396 break; 397 case UPDATE_SERVICE_SUCCEEDED: 398 if (mDnsSdUpdateListener != null) { 399 mDnsSdUpdateListener.onServiceUpdated(message.arg1, 400 (DnsSdTxtRecord) message.obj); 401 } 402 break; 403 case RESOLVE_SERVICE_FAILED: 404 if (mDnsSdResolveListener != null) { 405 mDnsSdResolveListener.onFailure(message.arg1); 406 } 407 break; 408 case RESOLVE_SERVICE_SUCCEEDED: 409 if (mDnsSdResolveListener != null) { 410 mDnsSdResolveListener.onServiceResolved( 411 (DnsSdServiceInfo) message.obj); 412 } 413 break; 414 case STOP_RESOLVE_FAILED: 415 if (mDnsSdStopResolveListener!= null) { 416 mDnsSdStopResolveListener.onFailure(message.arg1); 417 } 418 break; 419 case STOP_RESOLVE_SUCCEEDED: 420 if (mDnsSdStopResolveListener != null) { 421 mDnsSdStopResolveListener.onSuccess(); 422 } 423 break; 424 default: 425 Log.d(TAG, "Ignored " + message); 426 break; 427 } 428 } 429 } 430 } 431 432 private static void checkChannel(Channel c) { 433 if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); 434 } 435 436 /** 437 * Registers the application with the service discovery framework. This function 438 * must be the first to be called before any other operations are performed. No service 439 * discovery operations must be performed until the ChannelListener callback notifies 440 * that the channel is connected 441 * 442 * @param srcContext is the context of the source 443 * @param srcLooper is the Looper on which the callbacks are receivied 444 * @param listener for callback at loss of framework communication. Cannot be null. 445 */ 446 public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { 447 Messenger messenger = getMessenger(); 448 if (messenger == null) throw new RuntimeException("Failed to initialize"); 449 if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); 450 451 Channel c = new Channel(srcLooper, listener); 452 c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); 453 } 454 455 /** 456 * Disconnects application from service discovery framework. No further operations 457 * will succeed until a {@link #initialize} is called again. 458 * 459 * @param c channel initialized with {@link #initialize} 460 */ 461 public void deinitialize(Channel c) { 462 checkChannel(c); 463 c.mAsyncChannel.disconnect(); 464 } 465 466 /** 467 * Register a service to be discovered by other services. 468 * 469 * <p> The function call immediately returns after sending a request to register service 470 * to the framework. The application is notified of a success to initiate 471 * discovery through the callback {@link DnsSdRegisterListener#onServiceRegistered} or a failure 472 * through {@link DnsSdRegisterListener#onFailure}. 473 * 474 * @param c is the channel created at {@link #initialize} 475 * @param serviceType The service type being advertised. 476 * @param port on which the service is listenering for incoming connections 477 * @param listener for success or failure callback. Can be null. 478 */ 479 public void registerService(Channel c, String serviceName, String serviceType, int port, 480 DnsSdRegisterListener listener) { 481 checkChannel(c); 482 if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) { 483 throw new IllegalArgumentException("Service name or type cannot be empty"); 484 } 485 if (port <= 0) { 486 throw new IllegalArgumentException("Invalid port number"); 487 } 488 DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null); 489 serviceInfo.setPort(port); 490 c.mDnsSdRegisterListener = listener; 491 c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo); 492 } 493 494 /** 495 * Unregister a service registered through {@link #registerService} 496 * @param c is the channel created at {@link #initialize} 497 * @param registeredId is obtained at {@link DnsSdRegisterListener#onServiceRegistered} 498 * @param listener provides callbacks for success or failure. Can be null. 499 */ 500 public void unregisterService(Channel c, int registeredId, ActionListener listener) { 501 checkChannel(c); 502 c.mDnsSdUnregisterListener = listener; 503 c.mAsyncChannel.sendMessage(UNREGISTER_SERVICE, registeredId); 504 } 505 506 /** @hide */ 507 public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) { 508 checkChannel(c); 509 c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); 510 } 511 512 /** 513 * Initiate service discovery to browse for instances of a service type. Service discovery 514 * consumes network bandwidth and will continue until the application calls 515 * {@link #stopServiceDiscovery}. 516 * 517 * <p> The function call immediately returns after sending a request to start service 518 * discovery to the framework. The application is notified of a success to initiate 519 * discovery through the callback {@link DnsSdDiscoveryListener#onStarted} or a failure 520 * through {@link DnsSdDiscoveryListener#onFailure}. 521 * 522 * <p> Upon successful start, application is notified when a service is found with 523 * {@link DnsSdDiscoveryListener#onServiceFound} or when a service is lost with 524 * {@link DnsSdDiscoveryListener#onServiceLost}. 525 * 526 * <p> Upon failure to start, service discovery is not active and application does 527 * not need to invoke {@link #stopServiceDiscovery} 528 * 529 * @param c is the channel created at {@link #initialize} 530 * @param serviceType The service type being discovered. Examples include "_http._tcp" for 531 * http services or "_ipp._tcp" for printers 532 * @param listener provides callbacks when service is found or lost. Cannot be null. 533 */ 534 public void discoverServices(Channel c, String serviceType, DnsSdDiscoveryListener listener) { 535 checkChannel(c); 536 if (listener == null) { 537 throw new IllegalStateException("Discovery listener needs to be set first"); 538 } 539 if (TextUtils.isEmpty(serviceType)) { 540 throw new IllegalStateException("Service type cannot be empty"); 541 } 542 DnsSdServiceInfo s = new DnsSdServiceInfo(); 543 s.setServiceType(serviceType); 544 c.mDnsSdDiscoveryListener = listener; 545 c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); 546 } 547 548 /** 549 * Stop service discovery initiated with {@link #discoverServices}. An active service 550 * discovery is notified to the application with {@link DnsSdDiscoveryListener#onStarted} 551 * and it stays active until the application invokes a stop service discovery. 552 * 553 * <p> Upon failure to start service discovery notified through 554 * {@link DnsSdDiscoveryListener#onFailure} service discovery is not active and 555 * application does not need to stop it. 556 * 557 * @param c is the channel created at {@link #initialize} 558 * @param listener notifies success or failure. Can be null. 559 */ 560 public void stopServiceDiscovery(Channel c, ActionListener listener) { 561 checkChannel(c); 562 c.mDnsSdStopDiscoveryListener = listener; 563 c.mAsyncChannel.sendMessage(STOP_DISCOVERY); 564 } 565 566 /** 567 * Resolve a discovered service. An application can resolve a service right before 568 * establishing a connection to fetch the IP and port details on which to setup 569 * the connection. 570 * 571 * @param c is the channel created at {@link #initialize} 572 * @param serviceName of the the service 573 * @param serviceType of the service 574 * @param listener to receive callback upon success or failure. Cannot be null. 575 */ 576 public void resolveService(Channel c, String serviceName, String serviceType, 577 DnsSdResolveListener listener) { 578 checkChannel(c); 579 if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) { 580 throw new IllegalArgumentException("Service name or type cannot be empty"); 581 } 582 if (listener == null) throw new 583 IllegalStateException("Resolve listener cannot be null"); 584 c.mDnsSdResolveListener = listener; 585 DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null); 586 c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo); 587 } 588 589 /** @hide */ 590 public void stopServiceResolve(Channel c) { 591 checkChannel(c); 592 if (c.mDnsSdResolveListener == null) throw new 593 IllegalStateException("Resolve listener needs to be set first"); 594 c.mAsyncChannel.sendMessage(STOP_RESOLVE); 595 } 596 597 /** 598 * Get a reference to NetworkService handler. This is used to establish 599 * an AsyncChannel communication with the service 600 * 601 * @return Messenger pointing to the NetworkService handler 602 */ 603 private Messenger getMessenger() { 604 try { 605 return mService.getMessenger(); 606 } catch (RemoteException e) { 607 return null; 608 } 609 } 610} 611