NsdService.java revision 771cd657acc37b50bafe18bf5f649d3c1d85b3d8
1/*
2 * Copyright (C) 2010 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 com.android.server;
18
19import android.content.Context;
20import android.content.ContentResolver;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.database.ContentObserver;
24import android.net.nsd.NsdServiceInfo;
25import android.net.nsd.DnsSdTxtRecord;
26import android.net.nsd.INsdManager;
27import android.net.nsd.NsdManager;
28import android.os.Binder;
29import android.os.Message;
30import android.os.Messenger;
31import android.os.UserHandle;
32import android.provider.Settings;
33import android.util.Slog;
34import android.util.SparseArray;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import java.io.UnsupportedEncodingException;
39import java.net.InetAddress;
40import java.util.HashMap;
41import java.util.List;
42import java.util.Locale;
43import java.util.Map;
44import java.util.concurrent.CountDownLatch;
45
46import com.android.internal.util.AsyncChannel;
47import com.android.internal.util.Protocol;
48import com.android.internal.util.State;
49import com.android.internal.util.StateMachine;
50import com.android.server.NativeDaemonConnector.Command;
51
52/**
53 * Network Service Discovery Service handles remote service discovery operation requests by
54 * implementing the INsdManager interface.
55 *
56 * @hide
57 */
58public class NsdService extends INsdManager.Stub {
59    private static final String TAG = "NsdService";
60    private static final String MDNS_TAG = "mDnsConnector";
61
62    private static final boolean DBG = true;
63
64    private Context mContext;
65    private ContentResolver mContentResolver;
66    private NsdStateMachine mNsdStateMachine;
67
68    /**
69     * Clients receiving asynchronous messages
70     */
71    private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
72
73    /* A map from unique id to client info */
74    private SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<ClientInfo>();
75
76    private AsyncChannel mReplyChannel = new AsyncChannel();
77
78    private int INVALID_ID = 0;
79    private int mUniqueId = 1;
80
81    private static final int BASE = Protocol.BASE_NSD_MANAGER;
82    private static final int CMD_TO_STRING_COUNT = NsdManager.RESOLVE_SERVICE - BASE + 1;
83    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
84
85    static {
86        sCmdToString[NsdManager.DISCOVER_SERVICES - BASE] = "DISCOVER";
87        sCmdToString[NsdManager.STOP_DISCOVERY - BASE] = "STOP-DISCOVER";
88        sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER";
89        sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER";
90        sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE";
91    }
92
93    private static String cmdToString(int cmd) {
94        cmd -= BASE;
95        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
96            return sCmdToString[cmd];
97        } else {
98            return null;
99        }
100    }
101
102    private class NsdStateMachine extends StateMachine {
103
104        private final DefaultState mDefaultState = new DefaultState();
105        private final DisabledState mDisabledState = new DisabledState();
106        private final EnabledState mEnabledState = new EnabledState();
107
108        @Override
109        protected String getWhatToString(int what) {
110            return cmdToString(what);
111        }
112
113        /**
114         * Observes the NSD on/off setting, and takes action when changed.
115         */
116        private void registerForNsdSetting() {
117            ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
118                @Override
119                    public void onChange(boolean selfChange) {
120                        if (isNsdEnabled()) {
121                            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
122                        } else {
123                            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
124                        }
125                    }
126            };
127
128            mContext.getContentResolver().registerContentObserver(
129                    Settings.Global.getUriFor(Settings.Global.NSD_ON),
130                    false, contentObserver);
131        }
132
133        NsdStateMachine(String name) {
134            super(name);
135            addState(mDefaultState);
136                addState(mDisabledState, mDefaultState);
137                addState(mEnabledState, mDefaultState);
138            if (isNsdEnabled()) {
139                setInitialState(mEnabledState);
140            } else {
141                setInitialState(mDisabledState);
142            }
143            setLogRecSize(25);
144            registerForNsdSetting();
145        }
146
147        class DefaultState extends State {
148            @Override
149            public boolean processMessage(Message msg) {
150                ClientInfo cInfo = null;
151                switch (msg.what) {
152                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
153                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
154                            AsyncChannel c = (AsyncChannel) msg.obj;
155                            if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
156                            c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
157                            cInfo = new ClientInfo(c, msg.replyTo);
158                            mClients.put(msg.replyTo, cInfo);
159                        } else {
160                            Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
161                        }
162                        break;
163                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
164                        switch (msg.arg1) {
165                            case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
166                                Slog.e(TAG, "Send failed, client connection lost");
167                                break;
168                            case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
169                                if (DBG) Slog.d(TAG, "Client disconnected");
170                                break;
171                            default:
172                                if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
173                                break;
174                        }
175                        cInfo = mClients.get(msg.replyTo);
176                        if (cInfo != null) {
177                            cInfo.expungeAllRequests();
178                            mClients.remove(msg.replyTo);
179                        }
180                        //Last client
181                        if (mClients.size() == 0) {
182                            stopMDnsDaemon();
183                        }
184                        break;
185                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
186                        AsyncChannel ac = new AsyncChannel();
187                        ac.connect(mContext, getHandler(), msg.replyTo);
188                        break;
189                    case NsdManager.DISCOVER_SERVICES:
190                        replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
191                                NsdManager.FAILURE_INTERNAL_ERROR);
192                       break;
193                    case NsdManager.STOP_DISCOVERY:
194                       replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
195                               NsdManager.FAILURE_INTERNAL_ERROR);
196                        break;
197                    case NsdManager.REGISTER_SERVICE:
198                        replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
199                                NsdManager.FAILURE_INTERNAL_ERROR);
200                        break;
201                    case NsdManager.UNREGISTER_SERVICE:
202                        replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
203                                NsdManager.FAILURE_INTERNAL_ERROR);
204                        break;
205                    case NsdManager.RESOLVE_SERVICE:
206                        replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
207                                NsdManager.FAILURE_INTERNAL_ERROR);
208                        break;
209                    case NsdManager.NATIVE_DAEMON_EVENT:
210                    default:
211                        Slog.e(TAG, "Unhandled " + msg);
212                        return NOT_HANDLED;
213                }
214                return HANDLED;
215            }
216        }
217
218        class DisabledState extends State {
219            @Override
220            public void enter() {
221                sendNsdStateChangeBroadcast(false);
222            }
223
224            @Override
225            public boolean processMessage(Message msg) {
226                switch (msg.what) {
227                    case NsdManager.ENABLE:
228                        transitionTo(mEnabledState);
229                        break;
230                    default:
231                        return NOT_HANDLED;
232                }
233                return HANDLED;
234            }
235        }
236
237        class EnabledState extends State {
238            @Override
239            public void enter() {
240                sendNsdStateChangeBroadcast(true);
241                if (mClients.size() > 0) {
242                    startMDnsDaemon();
243                }
244            }
245
246            @Override
247            public void exit() {
248                if (mClients.size() > 0) {
249                    stopMDnsDaemon();
250                }
251            }
252
253            private boolean requestLimitReached(ClientInfo clientInfo) {
254                if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
255                    if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
256                    return true;
257                }
258                return false;
259            }
260
261            private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
262                clientInfo.mClientIds.put(clientId, globalId);
263                clientInfo.mClientRequests.put(clientId, what);
264                mIdToClientInfoMap.put(globalId, clientInfo);
265            }
266
267            private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
268                clientInfo.mClientIds.remove(clientId);
269                clientInfo.mClientRequests.remove(clientId);
270                mIdToClientInfoMap.remove(globalId);
271            }
272
273            @Override
274            public boolean processMessage(Message msg) {
275                ClientInfo clientInfo;
276                NsdServiceInfo servInfo;
277                boolean result = HANDLED;
278                int id;
279                switch (msg.what) {
280                  case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
281                        //First client
282                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
283                                mClients.size() == 0) {
284                            startMDnsDaemon();
285                        }
286                        result = NOT_HANDLED;
287                        break;
288                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
289                        result = NOT_HANDLED;
290                        break;
291                    case NsdManager.DISABLE:
292                        //TODO: cleanup clients
293                        transitionTo(mDisabledState);
294                        break;
295                    case NsdManager.DISCOVER_SERVICES:
296                        if (DBG) Slog.d(TAG, "Discover services");
297                        servInfo = (NsdServiceInfo) msg.obj;
298                        clientInfo = mClients.get(msg.replyTo);
299
300                        if (requestLimitReached(clientInfo)) {
301                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
302                                    NsdManager.FAILURE_MAX_LIMIT);
303                            break;
304                        }
305
306                        id = getUniqueId();
307                        if (discoverServices(id, servInfo.getServiceType())) {
308                            if (DBG) {
309                                Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
310                                        servInfo.getServiceType());
311                            }
312                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
313                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
314                        } else {
315                            stopServiceDiscovery(id);
316                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
317                                    NsdManager.FAILURE_INTERNAL_ERROR);
318                        }
319                        break;
320                    case NsdManager.STOP_DISCOVERY:
321                        if (DBG) Slog.d(TAG, "Stop service discovery");
322                        clientInfo = mClients.get(msg.replyTo);
323
324                        try {
325                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
326                        } catch (NullPointerException e) {
327                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
328                                    NsdManager.FAILURE_INTERNAL_ERROR);
329                            break;
330                        }
331                        removeRequestMap(msg.arg2, id, clientInfo);
332                        if (stopServiceDiscovery(id)) {
333                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
334                        } else {
335                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
336                                    NsdManager.FAILURE_INTERNAL_ERROR);
337                        }
338                        break;
339                    case NsdManager.REGISTER_SERVICE:
340                        if (DBG) Slog.d(TAG, "Register service");
341                        clientInfo = mClients.get(msg.replyTo);
342                        if (requestLimitReached(clientInfo)) {
343                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
344                                    NsdManager.FAILURE_MAX_LIMIT);
345                            break;
346                        }
347
348                        id = getUniqueId();
349                        if (registerService(id, (NsdServiceInfo) msg.obj)) {
350                            if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
351                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
352                            // Return success after mDns reports success
353                        } else {
354                            unregisterService(id);
355                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
356                                    NsdManager.FAILURE_INTERNAL_ERROR);
357                        }
358                        break;
359                    case NsdManager.UNREGISTER_SERVICE:
360                        if (DBG) Slog.d(TAG, "unregister service");
361                        clientInfo = mClients.get(msg.replyTo);
362                        try {
363                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
364                        } catch (NullPointerException e) {
365                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
366                                    NsdManager.FAILURE_INTERNAL_ERROR);
367                            break;
368                        }
369                        removeRequestMap(msg.arg2, id, clientInfo);
370                        if (unregisterService(id)) {
371                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
372                        } else {
373                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
374                                    NsdManager.FAILURE_INTERNAL_ERROR);
375                        }
376                        break;
377                    case NsdManager.RESOLVE_SERVICE:
378                        if (DBG) Slog.d(TAG, "Resolve service");
379                        servInfo = (NsdServiceInfo) msg.obj;
380                        clientInfo = mClients.get(msg.replyTo);
381
382
383                        if (clientInfo.mResolvedService != null) {
384                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
385                                    NsdManager.FAILURE_ALREADY_ACTIVE);
386                            break;
387                        }
388
389                        id = getUniqueId();
390                        if (resolveService(id, servInfo)) {
391                            clientInfo.mResolvedService = new NsdServiceInfo();
392                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
393                        } else {
394                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
395                                    NsdManager.FAILURE_INTERNAL_ERROR);
396                        }
397                        break;
398                    case NsdManager.NATIVE_DAEMON_EVENT:
399                        NativeEvent event = (NativeEvent) msg.obj;
400                        if (!handleNativeEvent(event.code, event.raw,
401                                NativeDaemonEvent.unescapeArgs(event.raw))) {
402                            result = NOT_HANDLED;
403                        }
404                        break;
405                    default:
406                        result = NOT_HANDLED;
407                        break;
408                }
409                return result;
410            }
411
412            private boolean handleNativeEvent(int code, String raw, String[] cooked) {
413                boolean handled = true;
414                NsdServiceInfo servInfo;
415                int id = Integer.parseInt(cooked[1]);
416                ClientInfo clientInfo = mIdToClientInfoMap.get(id);
417                if (clientInfo == null) {
418                    Slog.e(TAG, "Unique id with no client mapping: " + id);
419                    handled = false;
420                    return handled;
421                }
422
423                /* This goes in response as msg.arg2 */
424                int clientId = -1;
425                int keyId = clientInfo.mClientIds.indexOfValue(id);
426                if (keyId != -1) {
427                    clientId = clientInfo.mClientIds.keyAt(keyId);
428                } else {
429                    // This can happen because of race conditions. For example,
430                    // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
431                    // and we may get in this situation.
432                    Slog.d(TAG, "Notification for a listener that is no longer active: " + id);
433                    handled = false;
434                    return handled;
435                }
436
437                switch (code) {
438                    case NativeResponseCode.SERVICE_FOUND:
439                        /* NNN uniqueId serviceName regType domain */
440                        if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
441                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
442                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
443                                clientId, servInfo);
444                        break;
445                    case NativeResponseCode.SERVICE_LOST:
446                        /* NNN uniqueId serviceName regType domain */
447                        if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
448                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
449                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
450                                clientId, servInfo);
451                        break;
452                    case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
453                        /* NNN uniqueId errorCode */
454                        if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
455                        clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
456                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
457                        break;
458                    case NativeResponseCode.SERVICE_REGISTERED:
459                        /* NNN regId serviceName regType */
460                        if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
461                        servInfo = new NsdServiceInfo(cooked[2], null);
462                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
463                                id, clientId, servInfo);
464                        break;
465                    case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
466                        /* NNN regId errorCode */
467                        if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
468                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
469                               NsdManager.FAILURE_INTERNAL_ERROR, clientId);
470                        break;
471                    case NativeResponseCode.SERVICE_UPDATED:
472                        /* NNN regId */
473                        break;
474                    case NativeResponseCode.SERVICE_UPDATE_FAILED:
475                        /* NNN regId errorCode */
476                        break;
477                    case NativeResponseCode.SERVICE_RESOLVED:
478                        /* NNN resolveId fullName hostName port txtlen txtdata */
479                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
480                        int index = cooked[2].indexOf(".");
481                        if (index == -1) {
482                            Slog.e(TAG, "Invalid service found " + raw);
483                            break;
484                        }
485                        String name = cooked[2].substring(0, index);
486                        String rest = cooked[2].substring(index);
487                        String type = rest.replace(".local.", "");
488
489                        clientInfo.mResolvedService.setServiceName(name);
490                        clientInfo.mResolvedService.setServiceType(type);
491                        clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
492
493                        stopResolveService(id);
494                        removeRequestMap(clientId, id, clientInfo);
495
496                        int id2 = getUniqueId();
497                        if (getAddrInfo(id2, cooked[3])) {
498                            storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
499                        } else {
500                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
501                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
502                            clientInfo.mResolvedService = null;
503                        }
504                        break;
505                    case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
506                        /* NNN resolveId errorCode */
507                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
508                        stopResolveService(id);
509                        removeRequestMap(clientId, id, clientInfo);
510                        clientInfo.mResolvedService = null;
511                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
512                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
513                        break;
514                    case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
515                        /* NNN resolveId errorCode */
516                        stopGetAddrInfo(id);
517                        removeRequestMap(clientId, id, clientInfo);
518                        clientInfo.mResolvedService = null;
519                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
520                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
521                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
522                        break;
523                    case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
524                        /* NNN resolveId hostname ttl addr */
525                        if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
526                        try {
527                            clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
528                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
529                                   0, clientId, clientInfo.mResolvedService);
530                        } catch (java.net.UnknownHostException e) {
531                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
532                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
533                        }
534                        stopGetAddrInfo(id);
535                        removeRequestMap(clientId, id, clientInfo);
536                        clientInfo.mResolvedService = null;
537                        break;
538                    default:
539                        handled = false;
540                        break;
541                }
542                return handled;
543            }
544       }
545    }
546
547    private NativeDaemonConnector mNativeConnector;
548    private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
549
550    private NsdService(Context context) {
551        mContext = context;
552        mContentResolver = context.getContentResolver();
553
554        mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
555                MDNS_TAG, 25, null);
556
557        mNsdStateMachine = new NsdStateMachine(TAG);
558        mNsdStateMachine.start();
559
560        Thread th = new Thread(mNativeConnector, MDNS_TAG);
561        th.start();
562    }
563
564    public static NsdService create(Context context) throws InterruptedException {
565        NsdService service = new NsdService(context);
566        service.mNativeDaemonConnected.await();
567        return service;
568    }
569
570    public Messenger getMessenger() {
571        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
572            "NsdService");
573        return new Messenger(mNsdStateMachine.getHandler());
574    }
575
576    public void setEnabled(boolean enable) {
577        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
578                "NsdService");
579        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
580        if (enable) {
581            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
582        } else {
583            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
584        }
585    }
586
587    private void sendNsdStateChangeBroadcast(boolean enabled) {
588        final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
589        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
590        if (enabled) {
591            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
592        } else {
593            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
594        }
595        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
596    }
597
598    private boolean isNsdEnabled() {
599        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
600        if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
601        return ret;
602    }
603
604    private int getUniqueId() {
605        if (++mUniqueId == INVALID_ID) return ++mUniqueId;
606        return mUniqueId;
607    }
608
609    /* These should be in sync with system/netd/mDnsResponseCode.h */
610    class NativeResponseCode {
611        public static final int SERVICE_DISCOVERY_FAILED    =   602;
612        public static final int SERVICE_FOUND               =   603;
613        public static final int SERVICE_LOST                =   604;
614
615        public static final int SERVICE_REGISTRATION_FAILED =   605;
616        public static final int SERVICE_REGISTERED          =   606;
617
618        public static final int SERVICE_RESOLUTION_FAILED   =   607;
619        public static final int SERVICE_RESOLVED            =   608;
620
621        public static final int SERVICE_UPDATED             =   609;
622        public static final int SERVICE_UPDATE_FAILED       =   610;
623
624        public static final int SERVICE_GET_ADDR_FAILED     =   611;
625        public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
626    }
627
628    private class NativeEvent {
629        final int code;
630        final String raw;
631
632        NativeEvent(int code, String raw) {
633            this.code = code;
634            this.raw = raw;
635        }
636    }
637
638    class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
639        public void onDaemonConnected() {
640            mNativeDaemonConnected.countDown();
641        }
642
643        public boolean onCheckHoldWakeLock(int code) {
644            return false;
645        }
646
647        public boolean onEvent(int code, String raw, String[] cooked) {
648            // TODO: NDC translates a message to a callback, we could enhance NDC to
649            // directly interact with a state machine through messages
650            NativeEvent event = new NativeEvent(code, raw);
651            mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
652            return true;
653        }
654    }
655
656    private boolean startMDnsDaemon() {
657        if (DBG) Slog.d(TAG, "startMDnsDaemon");
658        try {
659            mNativeConnector.execute("mdnssd", "start-service");
660        } catch(NativeDaemonConnectorException e) {
661            Slog.e(TAG, "Failed to start daemon" + e);
662            return false;
663        }
664        return true;
665    }
666
667    private boolean stopMDnsDaemon() {
668        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
669        try {
670            mNativeConnector.execute("mdnssd", "stop-service");
671        } catch(NativeDaemonConnectorException e) {
672            Slog.e(TAG, "Failed to start daemon" + e);
673            return false;
674        }
675        return true;
676    }
677
678    private boolean registerService(int regId, NsdServiceInfo service) {
679        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
680        try {
681            Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
682                    service.getServiceType(), service.getPort());
683
684            // Add TXT records as additional arguments.
685            Map<String, byte[]> txtRecords = service.getAttributes();
686            for (String key : txtRecords.keySet()) {
687                try {
688                    // TODO: Send encoded TXT record as bytes once NDC/netd supports binary data.
689                    cmd.appendArg(String.format(Locale.US, "%s=%s", key,
690                            new String(txtRecords.get(key), "UTF_8")));
691                } catch (UnsupportedEncodingException e) {
692                    Slog.e(TAG, "Failed to encode txtRecord " + e);
693                }
694            }
695
696            mNativeConnector.execute(cmd);
697        } catch(NativeDaemonConnectorException e) {
698            Slog.e(TAG, "Failed to execute registerService " + e);
699            return false;
700        }
701        return true;
702    }
703
704    private boolean unregisterService(int regId) {
705        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
706        try {
707            mNativeConnector.execute("mdnssd", "stop-register", regId);
708        } catch(NativeDaemonConnectorException e) {
709            Slog.e(TAG, "Failed to execute unregisterService " + e);
710            return false;
711        }
712        return true;
713    }
714
715    private boolean updateService(int regId, DnsSdTxtRecord t) {
716        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
717        try {
718            if (t == null) return false;
719            mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
720        } catch(NativeDaemonConnectorException e) {
721            Slog.e(TAG, "Failed to updateServices " + e);
722            return false;
723        }
724        return true;
725    }
726
727    private boolean discoverServices(int discoveryId, String serviceType) {
728        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
729        try {
730            mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
731        } catch(NativeDaemonConnectorException e) {
732            Slog.e(TAG, "Failed to discoverServices " + e);
733            return false;
734        }
735        return true;
736    }
737
738    private boolean stopServiceDiscovery(int discoveryId) {
739        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
740        try {
741            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
742        } catch(NativeDaemonConnectorException e) {
743            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
744            return false;
745        }
746        return true;
747    }
748
749    private boolean resolveService(int resolveId, NsdServiceInfo service) {
750        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
751        try {
752            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
753                    service.getServiceType(), "local.");
754        } catch(NativeDaemonConnectorException e) {
755            Slog.e(TAG, "Failed to resolveService " + e);
756            return false;
757        }
758        return true;
759    }
760
761    private boolean stopResolveService(int resolveId) {
762        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
763        try {
764            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
765        } catch(NativeDaemonConnectorException e) {
766            Slog.e(TAG, "Failed to stop resolve " + e);
767            return false;
768        }
769        return true;
770    }
771
772    private boolean getAddrInfo(int resolveId, String hostname) {
773        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
774        try {
775            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
776        } catch(NativeDaemonConnectorException e) {
777            Slog.e(TAG, "Failed to getAddrInfo " + e);
778            return false;
779        }
780        return true;
781    }
782
783    private boolean stopGetAddrInfo(int resolveId) {
784        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
785        try {
786            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
787        } catch(NativeDaemonConnectorException e) {
788            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
789            return false;
790        }
791        return true;
792    }
793
794    @Override
795    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
796        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
797                != PackageManager.PERMISSION_GRANTED) {
798            pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid="
799                    + Binder.getCallingPid()
800                    + ", uid=" + Binder.getCallingUid());
801            return;
802        }
803
804        for (ClientInfo client : mClients.values()) {
805            pw.println("Client Info");
806            pw.println(client);
807        }
808
809        mNsdStateMachine.dump(fd, pw, args);
810    }
811
812    /* arg2 on the source message has an id that needs to be retained in replies
813     * see NsdManager for details */
814    private Message obtainMessage(Message srcMsg) {
815        Message msg = Message.obtain();
816        msg.arg2 = srcMsg.arg2;
817        return msg;
818    }
819
820    private void replyToMessage(Message msg, int what) {
821        if (msg.replyTo == null) return;
822        Message dstMsg = obtainMessage(msg);
823        dstMsg.what = what;
824        mReplyChannel.replyToMessage(msg, dstMsg);
825    }
826
827    private void replyToMessage(Message msg, int what, int arg1) {
828        if (msg.replyTo == null) return;
829        Message dstMsg = obtainMessage(msg);
830        dstMsg.what = what;
831        dstMsg.arg1 = arg1;
832        mReplyChannel.replyToMessage(msg, dstMsg);
833    }
834
835    private void replyToMessage(Message msg, int what, Object obj) {
836        if (msg.replyTo == null) return;
837        Message dstMsg = obtainMessage(msg);
838        dstMsg.what = what;
839        dstMsg.obj = obj;
840        mReplyChannel.replyToMessage(msg, dstMsg);
841    }
842
843    /* Information tracked per client */
844    private class ClientInfo {
845
846        private static final int MAX_LIMIT = 10;
847        private final AsyncChannel mChannel;
848        private final Messenger mMessenger;
849        /* Remembers a resolved service until getaddrinfo completes */
850        private NsdServiceInfo mResolvedService;
851
852        /* A map from client id to unique id sent to mDns */
853        private SparseArray<Integer> mClientIds = new SparseArray<Integer>();
854
855        /* A map from client id to the type of the request we had received */
856        private SparseArray<Integer> mClientRequests = new SparseArray<Integer>();
857
858        private ClientInfo(AsyncChannel c, Messenger m) {
859            mChannel = c;
860            mMessenger = m;
861            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
862        }
863
864        @Override
865        public String toString() {
866            StringBuffer sb = new StringBuffer();
867            sb.append("mChannel ").append(mChannel).append("\n");
868            sb.append("mMessenger ").append(mMessenger).append("\n");
869            sb.append("mResolvedService ").append(mResolvedService).append("\n");
870            for(int i = 0; i< mClientIds.size(); i++) {
871                int clientID = mClientIds.keyAt(i);
872                sb.append("clientId ").append(clientID).
873                    append(" mDnsId ").append(mClientIds.valueAt(i)).
874                    append(" type ").append(mClientRequests.get(clientID)).append("\n");
875            }
876            return sb.toString();
877        }
878
879        // Remove any pending requests from the global map when we get rid of a client,
880        // and send cancellations to the daemon.
881        private void expungeAllRequests() {
882            int globalId, clientId, i;
883            for (i = 0; i < mClientIds.size(); i++) {
884                clientId = mClientIds.keyAt(i);
885                globalId = mClientIds.valueAt(i);
886                mIdToClientInfoMap.remove(globalId);
887                if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
888                        " global-ID " + globalId + " type " + mClientRequests.get(clientId));
889                switch (mClientRequests.get(clientId)) {
890                    case NsdManager.DISCOVER_SERVICES:
891                        stopServiceDiscovery(globalId);
892                        break;
893                    case NsdManager.RESOLVE_SERVICE:
894                        stopResolveService(globalId);
895                        break;
896                    case NsdManager.REGISTER_SERVICE:
897                        unregisterService(globalId);
898                        break;
899                    default:
900                        break;
901                }
902            }
903            mClientIds.clear();
904            mClientRequests.clear();
905        }
906
907    }
908}
909