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