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