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