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