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