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
17package android.net.nsd;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.Context;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteException;
27import android.os.Messenger;
28import android.text.TextUtils;
29import android.util.Log;
30import android.util.SparseArray;
31
32import java.util.concurrent.CountDownLatch;
33
34import com.android.internal.util.AsyncChannel;
35import 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 */
121public 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            throw e.rethrowFromSystemServer();
624        }
625    }
626
627    /**
628     * Get a reference to NetworkService handler. This is used to establish
629     * an AsyncChannel communication with the service
630     *
631     * @return Messenger pointing to the NetworkService handler
632     */
633    private Messenger getMessenger() {
634        try {
635            return mService.getMessenger();
636        } catch (RemoteException e) {
637            throw e.rethrowFromSystemServer();
638        }
639    }
640}
641