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 switch (message.what) { 305 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 306 mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 307 return; 308 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 309 mConnected.countDown(); 310 return; 311 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 312 Log.e(TAG, "Channel lost"); 313 return; 314 default: 315 break; 316 } 317 Object listener = getListener(message.arg2); 318 if (listener == null) { 319 Log.d(TAG, "Stale key " + message.arg2); 320 return; 321 } 322 boolean listenerRemove = true; 323 NsdServiceInfo ns = getNsdService(message.arg2); 324 switch (message.what) { 325 case DISCOVER_SERVICES_STARTED: 326 String s = getNsdServiceInfoType((NsdServiceInfo) message.obj); 327 ((DiscoveryListener) listener).onDiscoveryStarted(s); 328 // Keep listener until stop discovery 329 listenerRemove = false; 330 break; 331 case DISCOVER_SERVICES_FAILED: 332 ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns), 333 message.arg1); 334 break; 335 case SERVICE_FOUND: 336 ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj); 337 // Keep listener until stop discovery 338 listenerRemove = false; 339 break; 340 case SERVICE_LOST: 341 ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj); 342 // Keep listener until stop discovery 343 listenerRemove = false; 344 break; 345 case STOP_DISCOVERY_FAILED: 346 ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns), 347 message.arg1); 348 break; 349 case STOP_DISCOVERY_SUCCEEDED: 350 ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns)); 351 break; 352 case REGISTER_SERVICE_FAILED: 353 ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1); 354 break; 355 case REGISTER_SERVICE_SUCCEEDED: 356 ((RegistrationListener) listener).onServiceRegistered( 357 (NsdServiceInfo) message.obj); 358 // Keep listener until unregister 359 listenerRemove = false; 360 break; 361 case UNREGISTER_SERVICE_FAILED: 362 ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1); 363 break; 364 case UNREGISTER_SERVICE_SUCCEEDED: 365 ((RegistrationListener) listener).onServiceUnregistered(ns); 366 break; 367 case RESOLVE_SERVICE_FAILED: 368 ((ResolveListener) listener).onResolveFailed(ns, message.arg1); 369 break; 370 case RESOLVE_SERVICE_SUCCEEDED: 371 ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj); 372 break; 373 default: 374 Log.d(TAG, "Ignored " + message); 375 break; 376 } 377 if (listenerRemove) { 378 removeListener(message.arg2); 379 } 380 } 381 } 382 383 private int putListener(Object listener, NsdServiceInfo s) { 384 if (listener == null) return INVALID_LISTENER_KEY; 385 int key; 386 synchronized (mMapLock) { 387 do { 388 key = mListenerKey++; 389 } while (key == INVALID_LISTENER_KEY); 390 mListenerMap.put(key, listener); 391 mServiceMap.put(key, s); 392 } 393 return key; 394 } 395 396 private Object getListener(int key) { 397 if (key == INVALID_LISTENER_KEY) return null; 398 synchronized (mMapLock) { 399 return mListenerMap.get(key); 400 } 401 } 402 403 private NsdServiceInfo getNsdService(int key) { 404 synchronized (mMapLock) { 405 return mServiceMap.get(key); 406 } 407 } 408 409 private void removeListener(int key) { 410 if (key == INVALID_LISTENER_KEY) return; 411 synchronized (mMapLock) { 412 mListenerMap.remove(key); 413 mServiceMap.remove(key); 414 } 415 } 416 417 private int getListenerKey(Object listener) { 418 synchronized (mMapLock) { 419 int valueIndex = mListenerMap.indexOfValue(listener); 420 if (valueIndex != -1) { 421 return mListenerMap.keyAt(valueIndex); 422 } 423 } 424 return INVALID_LISTENER_KEY; 425 } 426 427 428 private String getNsdServiceInfoType(NsdServiceInfo s) { 429 if (s == null) return "?"; 430 return s.getServiceType(); 431 } 432 433 /** 434 * Initialize AsyncChannel 435 */ 436 private void init() { 437 final Messenger messenger = getMessenger(); 438 if (messenger == null) throw new RuntimeException("Failed to initialize"); 439 HandlerThread t = new HandlerThread("NsdManager"); 440 t.start(); 441 mHandler = new ServiceHandler(t.getLooper()); 442 mAsyncChannel.connect(mContext, mHandler, messenger); 443 try { 444 mConnected.await(); 445 } catch (InterruptedException e) { 446 Log.e(TAG, "interrupted wait at init"); 447 } 448 } 449 450 /** 451 * Register a service to be discovered by other services. 452 * 453 * <p> The function call immediately returns after sending a request to register service 454 * to the framework. The application is notified of a success to initiate 455 * discovery through the callback {@link RegistrationListener#onServiceRegistered} or a failure 456 * through {@link RegistrationListener#onRegistrationFailed}. 457 * 458 * @param serviceInfo The service being registered 459 * @param protocolType The service discovery protocol 460 * @param listener The listener notifies of a successful registration and is used to 461 * unregister this service through a call on {@link #unregisterService}. Cannot be null. 462 */ 463 public void registerService(NsdServiceInfo serviceInfo, int protocolType, 464 RegistrationListener listener) { 465 if (TextUtils.isEmpty(serviceInfo.getServiceName()) || 466 TextUtils.isEmpty(serviceInfo.getServiceType())) { 467 throw new IllegalArgumentException("Service name or type cannot be empty"); 468 } 469 if (serviceInfo.getPort() <= 0) { 470 throw new IllegalArgumentException("Invalid port number"); 471 } 472 if (listener == null) { 473 throw new IllegalArgumentException("listener cannot be null"); 474 } 475 if (protocolType != PROTOCOL_DNS_SD) { 476 throw new IllegalArgumentException("Unsupported protocol"); 477 } 478 mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, putListener(listener, serviceInfo), 479 serviceInfo); 480 } 481 482 /** 483 * Unregister a service registered through {@link #registerService}. A successful 484 * unregister is notified to the application with a call to 485 * {@link RegistrationListener#onServiceUnregistered}. 486 * 487 * @param listener This should be the listener object that was passed to 488 * {@link #registerService}. It identifies the service that should be unregistered 489 * and notifies of a successful unregistration. 490 */ 491 public void unregisterService(RegistrationListener listener) { 492 int id = getListenerKey(listener); 493 if (id == INVALID_LISTENER_KEY) { 494 throw new IllegalArgumentException("listener not registered"); 495 } 496 if (listener == null) { 497 throw new IllegalArgumentException("listener cannot be null"); 498 } 499 mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id); 500 } 501 502 /** 503 * Initiate service discovery to browse for instances of a service type. Service discovery 504 * consumes network bandwidth and will continue until the application calls 505 * {@link #stopServiceDiscovery}. 506 * 507 * <p> The function call immediately returns after sending a request to start service 508 * discovery to the framework. The application is notified of a success to initiate 509 * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure 510 * through {@link DiscoveryListener#onStartDiscoveryFailed}. 511 * 512 * <p> Upon successful start, application is notified when a service is found with 513 * {@link DiscoveryListener#onServiceFound} or when a service is lost with 514 * {@link DiscoveryListener#onServiceLost}. 515 * 516 * <p> Upon failure to start, service discovery is not active and application does 517 * not need to invoke {@link #stopServiceDiscovery} 518 * 519 * @param serviceType The service type being discovered. Examples include "_http._tcp" for 520 * http services or "_ipp._tcp" for printers 521 * @param protocolType The service discovery protocol 522 * @param listener The listener notifies of a successful discovery and is used 523 * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. 524 * Cannot be null. 525 */ 526 public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { 527 if (listener == null) { 528 throw new IllegalArgumentException("listener cannot be null"); 529 } 530 if (TextUtils.isEmpty(serviceType)) { 531 throw new IllegalArgumentException("Service type cannot be empty"); 532 } 533 534 if (protocolType != PROTOCOL_DNS_SD) { 535 throw new IllegalArgumentException("Unsupported protocol"); 536 } 537 538 NsdServiceInfo s = new NsdServiceInfo(); 539 s.setServiceType(serviceType); 540 mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, putListener(listener, s), s); 541 } 542 543 /** 544 * Stop service discovery initiated with {@link #discoverServices}. An active service 545 * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted} 546 * and it stays active until the application invokes a stop service discovery. A successful 547 * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}. 548 * 549 * <p> Upon failure to stop service discovery, application is notified through 550 * {@link DiscoveryListener#onStopDiscoveryFailed}. 551 * 552 * @param listener This should be the listener object that was passed to {@link #discoverServices}. 553 * It identifies the discovery that should be stopped and notifies of a successful stop. 554 */ 555 public void stopServiceDiscovery(DiscoveryListener listener) { 556 int id = getListenerKey(listener); 557 if (id == INVALID_LISTENER_KEY) { 558 throw new IllegalArgumentException("service discovery not active on listener"); 559 } 560 if (listener == null) { 561 throw new IllegalArgumentException("listener cannot be null"); 562 } 563 mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id); 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 serviceInfo service to be resolved 572 * @param listener to receive callback upon success or failure. Cannot be null. 573 */ 574 public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) { 575 if (TextUtils.isEmpty(serviceInfo.getServiceName()) || 576 TextUtils.isEmpty(serviceInfo.getServiceType())) { 577 throw new IllegalArgumentException("Service name or type cannot be empty"); 578 } 579 if (listener == null) { 580 throw new IllegalArgumentException("listener cannot be null"); 581 } 582 mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, putListener(listener, serviceInfo), 583 serviceInfo); 584 } 585 586 /** Internal use only @hide */ 587 public void setEnabled(boolean enabled) { 588 try { 589 mService.setEnabled(enabled); 590 } catch (RemoteException e) { } 591 } 592 593 /** 594 * Get a reference to NetworkService handler. This is used to establish 595 * an AsyncChannel communication with the service 596 * 597 * @return Messenger pointing to the NetworkService handler 598 */ 599 private Messenger getMessenger() { 600 try { 601 return mService.getMessenger(); 602 } catch (RemoteException e) { 603 return null; 604 } 605 } 606 } 607