WifiScanningServiceImpl.java revision 84e718d76c7412121b64ed9ac497c8071f0ef5e9
1/*
2 * Copyright (C) 2008 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.wifi;
18
19import android.Manifest;
20import android.app.AlarmManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.net.wifi.IWifiScanner;
27import android.net.wifi.ScanResult;
28import android.net.wifi.WifiManager;
29import android.net.wifi.WifiScanner;
30import android.net.wifi.WifiScanner.BssidInfo;
31import android.net.wifi.WifiScanner.ChannelSpec;
32import android.net.wifi.WifiScanner.ScanData;
33import android.net.wifi.WifiScanner.ScanSettings;
34import android.net.wifi.WifiSsid;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.HandlerThread;
38import android.os.Looper;
39import android.os.Message;
40import android.os.Messenger;
41import android.os.RemoteException;
42import android.os.SystemClock;
43import android.os.WorkSource;
44import android.util.LocalLog;
45import android.util.Log;
46
47import com.android.internal.app.IBatteryStats;
48import com.android.internal.util.AsyncChannel;
49import com.android.internal.util.Protocol;
50import com.android.internal.util.StateMachine;
51import com.android.internal.util.State;
52import com.android.server.am.BatteryStatsService;
53
54import java.io.FileDescriptor;
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.Collection;
58import java.util.HashMap;
59import java.util.HashSet;
60import java.util.Iterator;
61import java.util.Map;
62
63public class WifiScanningServiceImpl extends IWifiScanner.Stub {
64
65    private static final String TAG = "WifiScanningService";
66    private static final boolean DBG = false;
67    private static final boolean VDBG = false;
68
69    private static final int INVALID_KEY = 0;                               // same as WifiScanner
70    private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
71    private static final int UNKNOWN_PID = -1;
72
73    private static final LocalLog mLocalLog = new LocalLog(1024);
74
75    private static void localLog(String message) {
76        mLocalLog.log(message);
77    }
78
79    private static void logw(String message) {
80        Log.w(TAG, message);
81        mLocalLog.log(message);
82    }
83
84    private static void loge(String message) {
85        Log.e(TAG, message);
86        mLocalLog.log(message);
87    }
88
89    @Override
90    public Messenger getMessenger() {
91        if (mClientHandler != null) {
92            return new Messenger(mClientHandler);
93        } else {
94            loge("WifiScanningServiceImpl trying to get messenger w/o initialization");
95            return null;
96        }
97    }
98
99    @Override
100    public Bundle getAvailableChannels(int band) {
101        ChannelSpec channelSpecs[] = getChannelsForBand(band);
102        ArrayList<Integer> list = new ArrayList<Integer>(channelSpecs.length);
103        for (ChannelSpec channelSpec : channelSpecs) {
104            list.add(channelSpec.frequency);
105        }
106        Bundle b = new Bundle();
107        b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list);
108        return b;
109    }
110
111    private void enforceLocationHardwarePermission(int uid) {
112        mContext.enforcePermission(
113                Manifest.permission.LOCATION_HARDWARE,
114                UNKNOWN_PID, uid,
115                "LocationHardware");
116    }
117
118    private class ClientHandler extends Handler {
119
120        ClientHandler(android.os.Looper looper) {
121            super(looper);
122        }
123
124        @Override
125        public void handleMessage(Message msg) {
126
127            if (DBG) localLog("ClientHandler got" + msg);
128
129            switch (msg.what) {
130
131                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
132                    if (msg.arg1 != AsyncChannel.STATUS_SUCCESSFUL) {
133                        loge("Client connection failure, error=" + msg.arg1);
134                    }
135                    return;
136                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
137                    AsyncChannel ac = new AsyncChannel();
138                    ac.connect(mContext, this, msg.replyTo);
139                    if (DBG) localLog("New client connected : " + msg.sendingUid + msg.replyTo);
140                    ClientInfo cInfo = new ClientInfo(msg.sendingUid, ac, msg.replyTo);
141                    mClients.put(msg.replyTo, cInfo);
142                    return;
143                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
144                    if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
145                        loge("Send failed, client connection lost");
146                    } else {
147                        if (DBG) localLog("Client connection lost with reason: " + msg.arg1);
148                    }
149                    if (DBG) localLog("closing client " + msg.replyTo);
150                    ClientInfo ci = mClients.remove(msg.replyTo);
151                    if (ci != null) {                       /* can be null if send failed above */
152                        if (DBG) localLog("closing client " + ci.mUid);
153                        ci.cleanup();
154                    }
155                    return;
156            }
157
158            try {
159                enforceLocationHardwarePermission(msg.sendingUid);
160            } catch (SecurityException e) {
161                localLog("failed to authorize app: " + e);
162                replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized");
163                return;
164            }
165
166            if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) {
167                mStateMachine.sendMessage(Message.obtain(msg));
168                return;
169            }
170            ClientInfo ci = mClients.get(msg.replyTo);
171            if (ci == null) {
172                loge("Could not find client info for message " + msg.replyTo);
173                replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
174                return;
175            }
176
177            int validCommands[] = {
178                    WifiScanner.CMD_SCAN,
179                    WifiScanner.CMD_START_BACKGROUND_SCAN,
180                    WifiScanner.CMD_STOP_BACKGROUND_SCAN,
181                    WifiScanner.CMD_START_SINGLE_SCAN,
182                    WifiScanner.CMD_STOP_SINGLE_SCAN,
183                    WifiScanner.CMD_SET_HOTLIST,
184                    WifiScanner.CMD_RESET_HOTLIST,
185                    WifiScanner.CMD_CONFIGURE_WIFI_CHANGE,
186                    WifiScanner.CMD_START_TRACKING_CHANGE,
187                    WifiScanner.CMD_STOP_TRACKING_CHANGE };
188
189            for (int cmd : validCommands) {
190                if (cmd == msg.what) {
191                    mStateMachine.sendMessage(Message.obtain(msg));
192                    return;
193                }
194            }
195
196            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request");
197        }
198    }
199
200    private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE;
201
202    private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
203    private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
204    private static final int CMD_HOTLIST_AP_FOUND                    = BASE + 2;
205    private static final int CMD_HOTLIST_AP_LOST                     = BASE + 3;
206    private static final int CMD_WIFI_CHANGE_DETECTED                = BASE + 4;
207    private static final int CMD_WIFI_CHANGES_STABILIZED             = BASE + 5;
208    private static final int CMD_DRIVER_LOADED                       = BASE + 6;
209    private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
210    private static final int CMD_SCAN_PAUSED                         = BASE + 8;
211    private static final int CMD_SCAN_RESTARTED                      = BASE + 9;
212    private static final int CMD_STOP_SCAN_INTERNAL                  = BASE + 10;
213
214    private Context mContext;
215    private WifiScanningStateMachine mStateMachine;
216    private ClientHandler mClientHandler;
217    private IBatteryStats mBatteryStats;
218    private final WifiNative.ScanCapabilities mScanCapabilities = new WifiNative.ScanCapabilities();
219
220    WifiScanningServiceImpl() { }
221
222    WifiScanningServiceImpl(Context context) {
223        mContext = context;
224    }
225
226    public void startService(Context context) {
227        mContext = context;
228
229        HandlerThread thread = new HandlerThread("WifiScanningService");
230        thread.start();
231
232        mClientHandler = new ClientHandler(thread.getLooper());
233        mStateMachine = new WifiScanningStateMachine(thread.getLooper());
234        mWifiChangeStateMachine = new WifiChangeStateMachine(thread.getLooper());
235        mBatteryStats = BatteryStatsService.getService();
236
237        mContext.registerReceiver(
238                new BroadcastReceiver() {
239                    @Override
240                    public void onReceive(Context context, Intent intent) {
241                        int state = intent.getIntExtra(
242                                WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
243                        if (DBG) localLog("SCAN_AVAILABLE : " + state);
244                        if (state == WifiManager.WIFI_STATE_ENABLED) {
245                            mStateMachine.sendMessage(CMD_DRIVER_LOADED);
246                        } else if (state == WifiManager.WIFI_STATE_DISABLED) {
247                            mStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
248                        }
249                    }
250                }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
251
252        mStateMachine.start();
253        mWifiChangeStateMachine.start();
254    }
255
256    class WifiScanningStateMachine extends StateMachine implements WifiNative.ScanEventHandler,
257            WifiNative.HotlistEventHandler, WifiNative.SignificantWifiChangeEventHandler {
258
259        private final DefaultState mDefaultState = new DefaultState();
260        private final StartedState mStartedState = new StartedState();
261        private final PausedState  mPausedState  = new PausedState();
262
263        public WifiScanningStateMachine(Looper looper) {
264            super(TAG, looper);
265
266            setLogRecSize(512);
267            setLogOnlyTransitions(false);
268            // setDbg(DBG);
269
270            addState(mDefaultState);
271                addState(mStartedState, mDefaultState);
272                addState(mPausedState, mDefaultState);
273
274            setInitialState(mDefaultState);
275        }
276
277        @Override
278        public void onScanResultsAvailable() {
279            if (DBG) localLog("onScanResultAvailable event received");
280            sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
281        }
282
283        @Override
284        public void onScanStatus() {
285            if (DBG) localLog("onScanStatus event received");
286            sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
287        }
288
289        @Override
290        public void onFullScanResult(ScanResult fullScanResult) {
291            if (DBG) localLog("Full scanresult received");
292            sendMessage(CMD_FULL_SCAN_RESULTS, 0, 0, fullScanResult);
293        }
294
295        @Override
296        public void onScanPaused(ScanData scanData[]) {
297            sendMessage(CMD_SCAN_PAUSED, scanData);
298        }
299
300        @Override
301        public void onScanRestarted() {
302            if (DBG) localLog("onScanRestarted() event received");
303            sendMessage(CMD_SCAN_RESTARTED);
304        }
305
306        @Override
307        public void onHotlistApFound(ScanResult[] results) {
308            if (DBG) localLog("HotlistApFound event received");
309            sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results);
310        }
311
312        @Override
313        public void onHotlistApLost(ScanResult[] results) {
314            if (DBG) localLog("HotlistApLost event received");
315            sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results);
316        }
317
318        @Override
319        public void onChangesFound(ScanResult[] results) {
320            if (DBG) localLog("onWifiChangesFound event received");
321            sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results);
322        }
323
324        class DefaultState extends State {
325            @Override
326            public void enter() {
327                if (DBG) localLog("DefaultState");
328            }
329            @Override
330            public boolean processMessage(Message msg) {
331
332                if (DBG) localLog("DefaultState got" + msg);
333
334                ClientInfo ci = mClients.get(msg.replyTo);
335
336                switch (msg.what) {
337                    case CMD_DRIVER_LOADED:
338                        if (WifiNative.getInterfaces() != 0) {
339                            if (WifiNative.getScanCapabilities(mScanCapabilities)) {
340                                transitionTo(mStartedState);
341                            } else {
342                                loge("could not get scan capabilities");
343                            }
344                        } else {
345                            loge("could not start HAL");
346                        }
347                        break;
348                    case WifiScanner.CMD_SCAN:
349                    case WifiScanner.CMD_START_BACKGROUND_SCAN:
350                    case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
351                    case WifiScanner.CMD_START_SINGLE_SCAN:
352                    case WifiScanner.CMD_STOP_SINGLE_SCAN:
353                    case WifiScanner.CMD_SET_HOTLIST:
354                    case WifiScanner.CMD_RESET_HOTLIST:
355                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
356                    case WifiScanner.CMD_START_TRACKING_CHANGE:
357                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
358                    case WifiScanner.CMD_GET_SCAN_RESULTS:
359                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
360                        break;
361
362                    case CMD_SCAN_RESULTS_AVAILABLE:
363                        if (DBG) localLog("ignored scan results available event");
364                        break;
365
366                    case CMD_FULL_SCAN_RESULTS:
367                        if (DBG) localLog("ignored full scan result event");
368                        break;
369
370                    default:
371                        break;
372                }
373
374                return HANDLED;
375            }
376        }
377
378        class StartedState extends State {
379
380            @Override
381            public void enter() {
382                if (DBG) localLog("StartedState");
383            }
384
385            @Override
386            public boolean processMessage(Message msg) {
387
388                if (DBG) localLog("StartedState got" + msg);
389
390                ClientInfo ci = mClients.get(msg.replyTo);
391
392                switch (msg.what) {
393                    case CMD_DRIVER_UNLOADED:
394                        transitionTo(mDefaultState);
395                        break;
396                    case WifiScanner.CMD_SCAN:
397                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not implemented");
398                        break;
399                    case WifiScanner.CMD_START_BACKGROUND_SCAN:
400                        if (addScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) {
401                            replySucceeded(msg);
402                        } else {
403                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
404                        }
405                        break;
406                    case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
407                        removeScanRequest(ci, msg.arg2);
408                        break;
409                    case WifiScanner.CMD_GET_SCAN_RESULTS:
410                        reportScanResults();
411                        replySucceeded(msg);
412                        break;
413                    case WifiScanner.CMD_START_SINGLE_SCAN:
414                        if (addSingleScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) {
415                            replySucceeded(msg);
416                        } else {
417                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
418                        }
419                        break;
420                    case WifiScanner.CMD_STOP_SINGLE_SCAN:
421                        removeScanRequest(ci, msg.arg2);
422                        break;
423                    case CMD_STOP_SCAN_INTERNAL:
424                        localLog("Removing single shot scan");
425                        removeScanRequest((ClientInfo) msg.obj, msg.arg2);
426                        break;
427                    case WifiScanner.CMD_SET_HOTLIST:
428                        setHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj);
429                        replySucceeded(msg);
430                        break;
431                    case WifiScanner.CMD_RESET_HOTLIST:
432                        resetHotlist(ci, msg.arg2);
433                        break;
434                    case WifiScanner.CMD_START_TRACKING_CHANGE:
435                        trackWifiChanges(ci, msg.arg2);
436                        replySucceeded(msg);
437                        break;
438                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
439                        untrackWifiChanges(ci, msg.arg2);
440                        break;
441                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
442                        configureWifiChange((WifiScanner.WifiChangeSettings) msg.obj);
443                        break;
444                    case CMD_SCAN_RESULTS_AVAILABLE: {
445                            ScanData[] results = WifiNative.getScanResults(/* flush = */ true);
446                            Collection<ClientInfo> clients = mClients.values();
447                            for (ClientInfo ci2 : clients) {
448                                ci2.reportScanResults(results);
449                            }
450                        }
451                        break;
452                    case CMD_FULL_SCAN_RESULTS: {
453                            ScanResult result = (ScanResult) msg.obj;
454                            if (DBG) localLog("reporting fullscan result for " + result.SSID);
455                            Collection<ClientInfo> clients = mClients.values();
456                            for (ClientInfo ci2 : clients) {
457                                ci2.reportFullScanResult(result);
458                            }
459                        }
460                        break;
461
462                    case CMD_HOTLIST_AP_FOUND: {
463                            ScanResult[] results = (ScanResult[])msg.obj;
464                            if (DBG) localLog("Found " + results.length + " results");
465                            Collection<ClientInfo> clients = mClients.values();
466                            for (ClientInfo ci2 : clients) {
467                                ci2.reportHotlistResults(WifiScanner.CMD_AP_FOUND, results);
468                            }
469                        }
470                        break;
471                    case CMD_HOTLIST_AP_LOST: {
472                            ScanResult[] results = (ScanResult[])msg.obj;
473                            if (DBG) localLog("Lost " + results.length + " results");
474                            Collection<ClientInfo> clients = mClients.values();
475                            for (ClientInfo ci2 : clients) {
476                                ci2.reportHotlistResults(WifiScanner.CMD_AP_LOST, results);
477                            }
478                        }
479                        break;
480                    case CMD_WIFI_CHANGE_DETECTED: {
481                            ScanResult[] results = (ScanResult[])msg.obj;
482                            reportWifiChanged(results);
483                        }
484                        break;
485                    case CMD_WIFI_CHANGES_STABILIZED: {
486                            ScanResult[] results = (ScanResult[])msg.obj;
487                            reportWifiStabilized(results);
488                        }
489                        break;
490                    case CMD_SCAN_PAUSED: {
491                            ScanData results[] = (ScanData[]) msg.obj;
492                            Collection<ClientInfo> clients = mClients.values();
493                            for (ClientInfo ci2 : clients) {
494                                ci2.reportScanResults(results);
495                            }
496                            transitionTo(mPausedState);
497                        }
498                        break;
499                    default:
500                        return NOT_HANDLED;
501                }
502
503                return HANDLED;
504            }
505        }
506
507        class PausedState extends State {
508            @Override
509            public void enter() {
510                if (DBG) localLog("PausedState");
511            }
512
513            @Override
514            public boolean processMessage(Message msg) {
515
516                if (DBG) localLog("PausedState got" + msg);
517
518                switch (msg.what) {
519                    case CMD_SCAN_RESTARTED:
520                        transitionTo(mStartedState);
521                        break;
522                    default:
523                        deferMessage(msg);
524                        break;
525                }
526                return HANDLED;
527            }
528
529        }
530
531        @Override
532        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
533            pw.println("number of clients : " + mClients.size());
534            for (ClientInfo client : mClients.values()) {
535                client.dump(fd, pw, args);
536                pw.append("------\n");
537            }
538            pw.println();
539            pw.println("localLog : ");
540            mLocalLog.dump(fd, pw, args);
541            pw.append("\n\n");
542            super.dump(fd, pw, args);
543        }
544    }
545
546    /* client management */
547    HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
548
549    private class ClientInfo {
550        private static final int MAX_LIMIT = 16;
551        private final AsyncChannel mChannel;
552        private final Messenger mMessenger;
553        private final int mUid;
554        private final WorkSource mWorkSource;
555        private boolean mScanWorkReported = false;
556
557        ClientInfo(int uid, AsyncChannel c, Messenger m) {
558            mChannel = c;
559            mMessenger = m;
560            mUid = uid;
561            mWorkSource = new WorkSource(uid, TAG);
562            if (DBG) localLog("New client, channel: " + c + " messenger: " + m);
563        }
564
565        void reportBatchedScanStart() {
566            if (mUid == 0)
567                return;
568
569            int csph = getCsph();
570
571            try {
572                mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph);
573                localLog("started scanning for UID " + mUid + ", csph = " + csph);
574            } catch (RemoteException e) {
575                logw("failed to report scan work: " + e.toString());
576            }
577        }
578
579        void reportBatchedScanStop() {
580            if (mUid == 0)
581                return;
582
583            try {
584                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource);
585                localLog("stopped scanning for UID " + mUid);
586            } catch (RemoteException e) {
587                logw("failed to cleanup scan work: " + e.toString());
588            }
589        }
590
591        int getCsph() {
592            int csph = 0;
593            for (ScanSettings settings : getScanSettings()) {
594                int num_channels = settings.channels == null ? 0 : settings.channels.length;
595                if (num_channels == 0 && settings.band != 0) {
596                    num_channels = getChannelsForBand(settings.band).length;
597                }
598
599                int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / settings.periodInMs;
600                csph += num_channels * scans_per_Hour;
601            }
602
603            return csph;
604        }
605
606        void reportScanWorkUpdate() {
607            if (mScanWorkReported) {
608                reportBatchedScanStop();
609                mScanWorkReported = false;
610            }
611            if (mScanSettings.isEmpty() == false) {
612                reportBatchedScanStart();
613                mScanWorkReported = true;
614            }
615        }
616
617        @Override
618        public String toString() {
619            StringBuffer sb = new StringBuffer();
620            sb.append("mChannel ").append(mChannel).append("\n");
621            sb.append("mMessenger ").append(mMessenger);
622            return sb.toString();
623        }
624
625        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
626            StringBuilder sb = new StringBuilder();
627            sb.append(toString());
628
629            Iterator<Map.Entry<Integer, ScanSettings>> it = mScanSettings.entrySet().iterator();
630            for (; it.hasNext(); ) {
631                Map.Entry<Integer, ScanSettings> entry = it.next();
632                sb.append("ScanId ").append(entry.getKey()).append("\n");
633
634                ScanSettings scanSettings = entry.getValue();
635                sb.append(describe(scanSettings));
636                sb.append("\n");
637            }
638
639            pw.println(sb.toString());
640        }
641
642        HashMap<Integer, ScanSettings> mScanSettings = new HashMap<Integer, ScanSettings>(4);
643        HashMap<Integer, Integer> mScanPeriods = new HashMap<Integer, Integer>(4);
644
645        void addScanRequest(ScanSettings settings, int id) {
646            mScanSettings.put(id, settings);
647            reportScanWorkUpdate();
648        }
649
650        void removeScanRequest(int id) {
651            ScanSettings settings = mScanSettings.remove(id);
652            if (settings != null && settings.periodInMs == 0) {
653                /* this was a single shot scan */
654                mChannel.sendMessage(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, id);
655            }
656            reportScanWorkUpdate();
657        }
658
659        Iterator<Map.Entry<Integer, ScanSettings>> getScans() {
660            return mScanSettings.entrySet().iterator();
661        }
662
663        Collection<ScanSettings> getScanSettings() {
664            return mScanSettings.values();
665        }
666
667        void reportScanResults(ScanData[] results) {
668            Iterator<Integer> it = mScanSettings.keySet().iterator();
669            while (it.hasNext()) {
670                int handler = it.next();
671                reportScanResults(results, handler);
672            }
673        }
674
675        void reportScanResults(ScanData[] results, int handler) {
676            ScanSettings settings = mScanSettings.get(handler);
677            ChannelSpec desiredChannels[] = settings.channels;
678            if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
679                    || desiredChannels == null || desiredChannels.length == 0)  {
680                desiredChannels = getChannelsForBand(settings.band);
681            }
682
683            // check the channels this client asked for ..
684            int num_results = 0;
685            for (ScanData result : results) {
686                boolean copyScanData = false;
687                for (ScanResult scanResult : result.getResults()) {
688                    for (ChannelSpec channelSpec : desiredChannels) {
689                        if (channelSpec.frequency == scanResult.frequency) {
690                            copyScanData = true;
691                            break;
692                        }
693                    }
694                    if (copyScanData) {
695                        num_results++;
696                        break;
697                    }
698                }
699            }
700
701            localLog("results = " + results.length + ", num_results = " + num_results);
702
703            ScanData results2[] = new ScanData[num_results];
704            int index = 0;
705            for (ScanData result : results) {
706                boolean copyScanData = false;
707                for (ScanResult scanResult : result.getResults()) {
708                    for (ChannelSpec channelSpec : desiredChannels) {
709                        if (channelSpec.frequency == scanResult.frequency) {
710                            copyScanData = true;
711                            break;
712                        }
713                    }
714                    if (copyScanData) {
715                        break;
716                    }
717                }
718
719                if (copyScanData) {
720                    if (VDBG) {
721                        localLog("adding at " + index);
722                    }
723                    results2[index] = new WifiScanner.ScanData(result);
724                    index++;
725                }
726            }
727
728            localLog("delivering results, num = " + results2.length);
729
730            deliverScanResults(handler, results2);
731            if (settings.periodInMs == 0) {
732                /* this is a single shot scan; stop the scan now */
733                mStateMachine.sendMessage(CMD_STOP_SCAN_INTERNAL, 0, handler, this);
734            }
735        }
736
737        void deliverScanResults(int handler, ScanData results[]) {
738            WifiScanner.ParcelableScanData parcelableScanData =
739                    new WifiScanner.ParcelableScanData(results);
740            mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData);
741        }
742
743        void reportFullScanResult(ScanResult result) {
744            Iterator<Integer> it = mScanSettings.keySet().iterator();
745            while (it.hasNext()) {
746                int handler = it.next();
747                ScanSettings settings = mScanSettings.get(handler);
748                ChannelSpec desiredChannels[] = settings.channels;
749                if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
750                        || desiredChannels == null || desiredChannels.length == 0)  {
751                    desiredChannels = getChannelsForBand(settings.band);
752                }
753                for (ChannelSpec channelSpec : desiredChannels) {
754                    if (channelSpec.frequency == result.frequency) {
755                        WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID);
756                        ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "",
757                                result.level, result.frequency, result.timestamp,
758                                ScanResult.UNSPECIFIED, ScanResult.UNSPECIFIED,result.channelWidth,
759                                result.centerFreq0, result.centerFreq1,
760                                result.is80211mcResponder());
761                        if (DBG) localLog("sending it to " + handler);
762                        newResult.informationElements = result.informationElements.clone();
763                        mChannel.sendMessage(
764                                WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult);
765                    }
766                }
767            }
768        }
769
770        void reportPeriodChanged(int handler, ScanSettings settings, int newPeriodInMs) {
771            Integer prevPeriodObject = mScanPeriods.get(handler);
772            int prevPeriodInMs = settings.periodInMs;
773            if (prevPeriodObject != null) {
774                prevPeriodInMs = prevPeriodObject;
775            }
776
777            if (prevPeriodInMs != newPeriodInMs) {
778                mChannel.sendMessage(WifiScanner.CMD_PERIOD_CHANGED, newPeriodInMs, handler);
779            }
780        }
781
782        HashMap<Integer, WifiScanner.HotlistSettings> mHotlistSettings =
783                new HashMap<Integer, WifiScanner.HotlistSettings>();
784
785        void addHostlistSettings(WifiScanner.HotlistSettings settings, int handler) {
786            mHotlistSettings.put(handler, settings);
787        }
788
789        void removeHostlistSettings(int handler) {
790            mHotlistSettings.remove(handler);
791        }
792
793        Collection<WifiScanner.HotlistSettings> getHotlistSettings() {
794            return mHotlistSettings.values();
795        }
796
797        void reportHotlistResults(int what, ScanResult[] results) {
798            Iterator<Map.Entry<Integer, WifiScanner.HotlistSettings>> it =
799                    mHotlistSettings.entrySet().iterator();
800            while (it.hasNext()) {
801                Map.Entry<Integer, WifiScanner.HotlistSettings> entry = it.next();
802                int handler = entry.getKey();
803                WifiScanner.HotlistSettings settings = entry.getValue();
804                int num_results = 0;
805                for (ScanResult result : results) {
806                    for (BssidInfo BssidInfo : settings.bssidInfos) {
807                        if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
808                            num_results++;
809                            break;
810                        }
811                    }
812                }
813
814                if (num_results == 0) {
815                    // nothing to report
816                    return;
817                }
818
819                ScanResult results2[] = new ScanResult[num_results];
820                int index = 0;
821                for (ScanResult result : results) {
822                    for (BssidInfo BssidInfo : settings.bssidInfos) {
823                        if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
824                            results2[index] = result;
825                            index++;
826                        }
827                    }
828                }
829
830                WifiScanner.ParcelableScanResults parcelableScanResults =
831                        new WifiScanner.ParcelableScanResults(results2);
832
833                mChannel.sendMessage(what, 0, handler, parcelableScanResults);
834            }
835        }
836
837        HashSet<Integer> mSignificantWifiHandlers = new HashSet<Integer>();
838        void addSignificantWifiChange(int handler) {
839            mSignificantWifiHandlers.add(handler);
840        }
841
842        void removeSignificantWifiChange(int handler) {
843            mSignificantWifiHandlers.remove(handler);
844        }
845
846        Collection<Integer> getWifiChangeHandlers() {
847            return mSignificantWifiHandlers;
848        }
849
850        void reportWifiChanged(ScanResult[] results) {
851            WifiScanner.ParcelableScanResults parcelableScanResults =
852                    new WifiScanner.ParcelableScanResults(results);
853            Iterator<Integer> it = mSignificantWifiHandlers.iterator();
854            while (it.hasNext()) {
855                int handler = it.next();
856                mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGE_DETECTED,
857                        0, handler, parcelableScanResults);
858            }
859        }
860
861        void reportWifiStabilized(ScanResult[] results) {
862            WifiScanner.ParcelableScanResults parcelableScanResults =
863                    new WifiScanner.ParcelableScanResults(results);
864            Iterator<Integer> it = mSignificantWifiHandlers.iterator();
865            while (it.hasNext()) {
866                int handler = it.next();
867                mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGES_STABILIZED,
868                        0, handler, parcelableScanResults);
869            }
870        }
871
872        void cleanup() {
873            mScanSettings.clear();
874            resetBuckets();
875
876            mHotlistSettings.clear();
877            resetHotlist();
878
879            for (Integer handler :  mSignificantWifiHandlers) {
880                untrackWifiChanges(this, handler);
881            }
882
883            mSignificantWifiHandlers.clear();
884            localLog("Successfully stopped all requests for client " + this);
885        }
886    }
887
888    void replySucceeded(Message msg) {
889        if (msg.replyTo != null) {
890            Message reply = Message.obtain();
891            reply.what = WifiScanner.CMD_OP_SUCCEEDED;
892            reply.arg2 = msg.arg2;
893            try {
894                msg.replyTo.send(reply);
895            } catch (RemoteException e) {
896                // There's not much we can do if reply can't be sent!
897            }
898        } else {
899            // locally generated message; doesn't need a reply!
900        }
901    }
902
903    void replyFailed(Message msg, int reason, String description) {
904        if (msg.replyTo != null) {
905            Message reply = Message.obtain();
906            reply.what = WifiScanner.CMD_OP_FAILED;
907            reply.arg2 = msg.arg2;
908            reply.obj = new WifiScanner.OperationResult(reason, description);
909            try {
910                msg.replyTo.send(reply);
911            } catch (RemoteException e) {
912                // There's not much we can do if reply can't be sent!
913            }
914        } else {
915            // locally generated message; doesn't need a reply!
916        }
917    }
918
919    private class SettingsComputer {
920
921        private class TimeBucket {
922            int periodInSecond;
923            int periodMinInSecond;
924            int periodMaxInSecond;
925
926            TimeBucket(int p, int min, int max) {
927                periodInSecond = p;
928                periodMinInSecond = min;
929                periodMaxInSecond = max;
930            }
931        }
932
933        private final TimeBucket[] mTimeBuckets = new TimeBucket[] {
934                new TimeBucket( 1, 0, 5 ),
935                new TimeBucket( 5, 5, 10 ),
936                new TimeBucket( 10, 10, 25 ),
937                new TimeBucket( 30, 25, 55 ),
938                new TimeBucket( 60, 55, 240),
939                new TimeBucket( 300, 240, 500),
940                new TimeBucket( 600, 500, 1500),
941                new TimeBucket( 1800, 1500, WifiScanner.MAX_SCAN_PERIOD_MS) };
942
943        private static final int MAX_CHANNELS = 32;
944        private static final int DEFAULT_BASE_PERIOD_MS = 5000;
945        private static final int DEFAULT_REPORT_THRESHOLD_NUM_SCANS = 10;
946        private static final int DEFAULT_REPORT_THRESHOLD_PERCENT = 100;
947
948        private WifiNative.ScanSettings mSettings;
949        {
950            mSettings = new WifiNative.ScanSettings();
951            mSettings.max_ap_per_scan = mScanCapabilities.max_ap_cache_per_scan;
952            mSettings.base_period_ms = DEFAULT_BASE_PERIOD_MS;
953            mSettings.report_threshold_percent = DEFAULT_REPORT_THRESHOLD_PERCENT;
954            mSettings.report_threshold_num_scans = DEFAULT_REPORT_THRESHOLD_NUM_SCANS;
955
956            mSettings.buckets = new WifiNative.BucketSettings[mScanCapabilities.max_scan_buckets];
957            for (int i = 0; i < mSettings.buckets.length; i++) {
958                WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
959                bucketSettings.bucket = i;
960                bucketSettings.report_events = 0;
961                bucketSettings.channels = new WifiNative.ChannelSettings[MAX_CHANNELS];
962                bucketSettings.num_channels = 0;
963                mSettings.buckets[i] = bucketSettings;
964            }
965        }
966
967        HashMap<Integer, Integer> mChannelToBucketMap = new HashMap<Integer, Integer>();
968
969        private int getBestBucket(ScanSettings settings) {
970
971            // check to see if any of the channels are being scanned already
972            // and find the smallest bucket index (it represents the quickest
973            // period of scan)
974
975            ChannelSpec channels[] = settings.channels;
976            if (channels == null) {
977                // set channels based on band
978                channels = getChannelsForBand(settings.band);
979            }
980
981            if (channels == null) {
982                // still no channels; then there's nothing to scan
983                loge("No channels to scan!!");
984                return -1;
985            }
986
987            int mostFrequentBucketIndex = mTimeBuckets.length;
988
989            for (ChannelSpec desiredChannelSpec : channels) {
990                if (mChannelToBucketMap.containsKey(desiredChannelSpec.frequency)) {
991                    int bucket = mChannelToBucketMap.get(desiredChannelSpec.frequency);
992                    if (bucket < mostFrequentBucketIndex) {
993                        mostFrequentBucketIndex = bucket;
994                    }
995                }
996            }
997
998            int bestBucketIndex = -1;                                   // best by period
999            for (int i = 0; i < mTimeBuckets.length; i++) {
1000                TimeBucket bucket = mTimeBuckets[i];
1001                if (bucket.periodMinInSecond * 1000 <= settings.periodInMs
1002                        && settings.periodInMs < bucket.periodMaxInSecond * 1000) {
1003                    // we set the time period to this
1004                    bestBucketIndex = i;
1005                    break;
1006                }
1007            }
1008
1009            if (mostFrequentBucketIndex < bestBucketIndex) {
1010                for (ChannelSpec desiredChannelSpec : channels) {
1011                    mChannelToBucketMap.put(desiredChannelSpec.frequency, mostFrequentBucketIndex);
1012                }
1013                localLog("returning mf bucket number " + mostFrequentBucketIndex);
1014                return mostFrequentBucketIndex;
1015            } else if (bestBucketIndex != -1) {
1016                for (ChannelSpec desiredChannelSpec : channels) {
1017                    mChannelToBucketMap.put(desiredChannelSpec.frequency, bestBucketIndex);
1018                }
1019                localLog("returning best bucket number " + bestBucketIndex);
1020                return bestBucketIndex;
1021            }
1022
1023            loge("Could not find suitable bucket for period " + settings.periodInMs);
1024            return -1;
1025        }
1026
1027        void prepChannelMap(ScanSettings settings) {
1028            getBestBucket(settings);
1029        }
1030
1031        int addScanRequestToBucket(ScanSettings settings) {
1032
1033            int bucketIndex = getBestBucket(settings);
1034            if (bucketIndex == -1) {
1035                loge("Ignoring invalid settings");
1036                return -1;
1037            }
1038
1039            ChannelSpec desiredChannels[] = settings.channels;
1040            if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
1041                    || desiredChannels == null
1042                    || desiredChannels.length == 0) {
1043                // set channels based on band
1044                desiredChannels = getChannelsForBand(settings.band);
1045                if (desiredChannels == null) {
1046                    // still no channels; then there's nothing to scan
1047                    loge("No channels to scan!!");
1048                    return -1;
1049                }
1050            }
1051
1052            // merge the channel lists for these buckets
1053            localLog("merging " + desiredChannels.length + " channels "
1054                    + " for period " + settings.periodInMs
1055                    + " maxScans " + settings.maxScansToCache);
1056
1057            WifiNative.BucketSettings bucket = mSettings.buckets[bucketIndex];
1058            boolean added = (bucket.num_channels == 0)
1059                    && (bucket.band == WifiScanner.WIFI_BAND_UNSPECIFIED);
1060            localLog("existing " + bucket.num_channels + " channels ");
1061
1062            HashSet<ChannelSpec> newChannels = new HashSet<ChannelSpec>();
1063            for (ChannelSpec desiredChannelSpec : desiredChannels) {
1064
1065                if (DBG) localLog("desired channel " + desiredChannelSpec.frequency);
1066
1067                boolean found = false;
1068                for (int i = 0; i < bucket.num_channels; i++) {
1069                    if (desiredChannelSpec.frequency == bucket.channels[i].frequency) {
1070                        found = true;
1071                        break;
1072                    }
1073                }
1074
1075                if (!found) {
1076                    newChannels.add(desiredChannelSpec);
1077                } else {
1078                    if (DBG) localLog("Already scanning channel " + desiredChannelSpec.frequency);
1079                }
1080            }
1081
1082            if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
1083                    || (bucket.num_channels + newChannels.size()) > bucket.channels.length) {
1084                // can't accommodate all channels; switch to specifying band
1085                bucket.num_channels = 0;
1086                bucket.band = getBandFromChannels(bucket.channels)
1087                        | getBandFromChannels(desiredChannels);
1088                bucket.channels = new WifiNative.ChannelSettings[0];
1089                localLog("switching to using band " + bucket.band);
1090            } else {
1091                for (ChannelSpec desiredChannelSpec : newChannels) {
1092
1093                    localLog("adding new channel spec " + desiredChannelSpec.frequency);
1094
1095                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
1096                    channelSettings.frequency = desiredChannelSpec.frequency;
1097                    bucket.channels[bucket.num_channels] = channelSettings;
1098                    bucket.num_channels++;
1099                    mChannelToBucketMap.put(bucketIndex, channelSettings.frequency);
1100                }
1101            }
1102
1103            if (bucket.report_events < settings.reportEvents) {
1104                if (DBG) localLog("setting report_events to " + settings.reportEvents);
1105                bucket.report_events = settings.reportEvents;
1106            } else {
1107                if (DBG) localLog("report_events is " + settings.reportEvents);
1108            }
1109
1110            if (added) {
1111                bucket.period_ms = mTimeBuckets[bucketIndex].periodInSecond * 1000;
1112                mSettings.num_buckets++;
1113            }
1114
1115            if ( settings.numBssidsPerScan != 0) {
1116                if (mSettings.max_ap_per_scan > settings.numBssidsPerScan) {
1117                    mSettings.max_ap_per_scan = settings.numBssidsPerScan;
1118                }
1119            }
1120
1121            if (settings.maxScansToCache != 0) {
1122                if (mSettings.report_threshold_num_scans > settings.maxScansToCache) {
1123                    mSettings.report_threshold_num_scans = settings.maxScansToCache;
1124                }
1125            }
1126
1127            return bucket.period_ms;
1128        }
1129
1130        public WifiNative.ScanSettings getComputedSettings() {
1131            return mSettings;
1132        }
1133
1134        public void compressBuckets() {
1135            int num_buckets = 0;
1136            for (int i = 0; i < mSettings.buckets.length; i++) {
1137                if (mSettings.buckets[i].num_channels != 0
1138                        || mSettings.buckets[i].band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
1139                    mSettings.buckets[num_buckets] = mSettings.buckets[i];
1140                    num_buckets++;
1141                }
1142            }
1143            // remove unused buckets
1144            for (int i = num_buckets; i < mSettings.buckets.length; i++) {
1145                mSettings.buckets[i] = null;
1146            }
1147
1148            mSettings.num_buckets = num_buckets;
1149            if (num_buckets != 0) {
1150                mSettings.base_period_ms = mSettings.buckets[0].period_ms;
1151            }
1152        }
1153    }
1154
1155    boolean resetBuckets() {
1156        SettingsComputer c = new SettingsComputer();
1157        Collection<ClientInfo> clients = mClients.values();
1158        for (ClientInfo ci : clients) {
1159            Collection<ScanSettings> settings = ci.getScanSettings();
1160            for (ScanSettings s : settings) {
1161                c.prepChannelMap(s);
1162            }
1163        }
1164
1165        for (ClientInfo ci : clients) {
1166            Iterator it = ci.getScans();
1167            while (it.hasNext()) {
1168                Map.Entry<Integer, ScanSettings> entry =
1169                        (Map.Entry<Integer,ScanSettings>)it.next();
1170                int id = entry.getKey();
1171                ScanSettings s = entry.getValue();
1172                int newPeriodInMs = c.addScanRequestToBucket(s);
1173                if (newPeriodInMs  == -1) {
1174                    if (DBG) localLog("could not find a good bucket");
1175                    return false;
1176                }
1177                if (newPeriodInMs != s.periodInMs) {
1178                    ci.reportPeriodChanged(id, s, newPeriodInMs);
1179                }
1180            }
1181        }
1182
1183        c.compressBuckets();
1184
1185        WifiNative.ScanSettings s = c.getComputedSettings();
1186        if (s.num_buckets == 0) {
1187            if (DBG) localLog("Stopping scan because there are no buckets");
1188            WifiNative.stopScan();
1189            return true;
1190        } else {
1191            if (WifiNative.startScan(s, mStateMachine)) {
1192                localLog("Successfully started scan of " + s.num_buckets + " buckets at"
1193                        + "time = " + SystemClock.elapsedRealtimeNanos() / 1000 + " period "
1194                        + s.base_period_ms);
1195                return true;
1196            } else {
1197                loge("Failed to start scan of " + s.num_buckets + " buckets");
1198                return false;
1199            }
1200        }
1201    }
1202
1203    void logScanRequest(String request, ClientInfo ci, int id, ScanSettings settings) {
1204        StringBuffer sb = new StringBuffer();
1205        sb.append(request);
1206        sb.append("\nClient ");
1207        sb.append(ci.toString());
1208        sb.append("\nId ");
1209        sb.append(id);
1210        sb.append("\n");
1211        if (settings != null) {
1212            sb.append(describe(settings));
1213            sb.append("\n");
1214        }
1215        sb.append("\n");
1216        localLog(sb.toString());
1217    }
1218
1219    boolean addScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
1220        // sanity check the input
1221        if (ci == null) {
1222            Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
1223            return false;
1224        }
1225        if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) {
1226            localLog("Failing scan request because periodInMs is " + settings.periodInMs);
1227            return false;
1228        }
1229
1230        int minSupportedPeriodMs = 0;
1231        if (settings.channels != null) {
1232            minSupportedPeriodMs = settings.channels.length * MIN_PERIOD_PER_CHANNEL_MS;
1233        } else {
1234            if ((settings.band & WifiScanner.WIFI_BAND_24_GHZ) == 0) {
1235                /* 2.4 GHz band has 11 to 13 channels */
1236                minSupportedPeriodMs += 1000;
1237            }
1238            if ((settings.band & WifiScanner.WIFI_BAND_5_GHZ) == 0) {
1239                /* 5 GHz band has another 10 channels */
1240                minSupportedPeriodMs += 1000;
1241            }
1242            if ((settings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) == 0) {
1243                /* DFS requires passive scan which takes longer time */
1244                minSupportedPeriodMs += 2000;
1245            }
1246        }
1247
1248        if (settings.periodInMs < minSupportedPeriodMs) {
1249            localLog("Failing scan request because minSupportedPeriodMs is "
1250                    + minSupportedPeriodMs + " but the request wants " + settings.periodInMs);
1251            return false;
1252        }
1253
1254        logScanRequest("addScanRequest", ci, handler, settings);
1255        ci.addScanRequest(settings, handler);
1256        if (resetBuckets()) {
1257            return true;
1258        } else {
1259            ci.removeScanRequest(handler);
1260            localLog("Failing scan request because failed to reset scan");
1261            return false;
1262        }
1263    }
1264
1265    boolean addSingleScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
1266        if (ci == null) {
1267            Log.d(TAG, "Failing single scan request ClientInfo not found " + handler);
1268            return false;
1269        }
1270        if (settings.reportEvents == 0) {
1271            settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
1272        }
1273        if (settings.periodInMs == 0) {
1274            settings.periodInMs = 10000;        // 10s - although second scan should never happen
1275        }
1276
1277        logScanRequest("addSingleScanRequest", ci, handler, settings);
1278        ci.addScanRequest(settings, handler);
1279        if (resetBuckets()) {
1280            /* reset periodInMs to 0 to indicate single shot scan */
1281            settings.periodInMs = 0;
1282            return true;
1283        } else {
1284            ci.removeScanRequest(handler);
1285            localLog("Failing scan request because failed to reset scan");
1286            return false;
1287        }
1288    }
1289
1290    void removeScanRequest(ClientInfo ci, int handler) {
1291        if (ci != null) {
1292            logScanRequest("removeScanRequest", ci, handler, null);
1293            ci.removeScanRequest(handler);
1294            resetBuckets();
1295        }
1296    }
1297
1298    boolean reportScanResults() {
1299        ScanData results[] = WifiNative.getScanResults(/* flush = */ true);
1300        Collection<ClientInfo> clients = mClients.values();
1301        for (ClientInfo ci2 : clients) {
1302            ci2.reportScanResults(results);
1303        }
1304
1305        return true;
1306    }
1307
1308    void resetHotlist() {
1309        Collection<ClientInfo> clients = mClients.values();
1310        int num_hotlist_ap = 0;
1311
1312        for (ClientInfo ci : clients) {
1313            Collection<WifiScanner.HotlistSettings> c = ci.getHotlistSettings();
1314            for (WifiScanner.HotlistSettings s : c) {
1315                num_hotlist_ap +=  s.bssidInfos.length;
1316            }
1317        }
1318
1319        if (num_hotlist_ap == 0) {
1320            WifiNative.resetHotlist();
1321        } else {
1322            BssidInfo bssidInfos[] = new BssidInfo[num_hotlist_ap];
1323            int index = 0;
1324            for (ClientInfo ci : clients) {
1325                Collection<WifiScanner.HotlistSettings> settings = ci.getHotlistSettings();
1326                for (WifiScanner.HotlistSettings s : settings) {
1327                    for (int i = 0; i < s.bssidInfos.length; i++, index++) {
1328                        bssidInfos[index] = s.bssidInfos[i];
1329                    }
1330                }
1331            }
1332
1333            WifiScanner.HotlistSettings settings = new WifiScanner.HotlistSettings();
1334            settings.bssidInfos = bssidInfos;
1335            settings.apLostThreshold = 3;
1336            WifiNative.setHotlist(settings, mStateMachine);
1337        }
1338    }
1339
1340    void setHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) {
1341        ci.addHostlistSettings(settings, handler);
1342        resetHotlist();
1343    }
1344
1345    void resetHotlist(ClientInfo ci, int handler) {
1346        ci.removeHostlistSettings(handler);
1347        resetHotlist();
1348    }
1349
1350    WifiChangeStateMachine mWifiChangeStateMachine;
1351
1352    void trackWifiChanges(ClientInfo ci, int handler) {
1353        mWifiChangeStateMachine.enable();
1354        ci.addSignificantWifiChange(handler);
1355    }
1356
1357    void untrackWifiChanges(ClientInfo ci, int handler) {
1358        ci.removeSignificantWifiChange(handler);
1359        Collection<ClientInfo> clients = mClients.values();
1360        for (ClientInfo ci2 : clients) {
1361            if (ci2.getWifiChangeHandlers().size() != 0) {
1362                // there is at least one client watching for
1363                // significant changes; so nothing more to do
1364                return;
1365            }
1366        }
1367
1368        // no more clients looking for significant wifi changes
1369        // no need to keep the state machine running; disable it
1370        mWifiChangeStateMachine.disable();
1371    }
1372
1373    void configureWifiChange(WifiScanner.WifiChangeSettings settings) {
1374        mWifiChangeStateMachine.configure(settings);
1375    }
1376
1377    void reportWifiChanged(ScanResult results[]) {
1378        Collection<ClientInfo> clients = mClients.values();
1379        for (ClientInfo ci : clients) {
1380            ci.reportWifiChanged(results);
1381        }
1382    }
1383
1384    void reportWifiStabilized(ScanResult results[]) {
1385        Collection<ClientInfo> clients = mClients.values();
1386        for (ClientInfo ci : clients) {
1387            ci.reportWifiStabilized(results);
1388        }
1389    }
1390
1391    class WifiChangeStateMachine extends StateMachine
1392            implements WifiNative.SignificantWifiChangeEventHandler {
1393
1394        private static final String TAG = "WifiChangeStateMachine";
1395
1396        private static final int WIFI_CHANGE_CMD_NEW_SCAN_RESULTS           = 0;
1397        private static final int WIFI_CHANGE_CMD_CHANGE_DETECTED            = 1;
1398        private static final int WIFI_CHANGE_CMD_CHANGE_TIMEOUT             = 2;
1399        private static final int WIFI_CHANGE_CMD_ENABLE                     = 3;
1400        private static final int WIFI_CHANGE_CMD_DISABLE                    = 4;
1401        private static final int WIFI_CHANGE_CMD_CONFIGURE                  = 5;
1402
1403        private static final int MAX_APS_TO_TRACK = 3;
1404        private static final int MOVING_SCAN_PERIOD_MS      = 10000;
1405        private static final int STATIONARY_SCAN_PERIOD_MS  =  5000;
1406        private static final int MOVING_STATE_TIMEOUT_MS    = 30000;
1407
1408        State mDefaultState = new DefaultState();
1409        State mStationaryState = new StationaryState();
1410        State mMovingState = new MovingState();
1411
1412        private static final String ACTION_TIMEOUT =
1413                "com.android.server.WifiScanningServiceImpl.action.TIMEOUT";
1414        AlarmManager  mAlarmManager;
1415        PendingIntent mTimeoutIntent;
1416        ScanResult    mCurrentBssids[];
1417
1418        WifiChangeStateMachine(Looper looper) {
1419            super("SignificantChangeStateMachine", looper);
1420
1421            mClients.put(null, mClientInfo);
1422
1423            addState(mDefaultState);
1424            addState(mStationaryState, mDefaultState);
1425            addState(mMovingState, mDefaultState);
1426
1427            setInitialState(mDefaultState);
1428        }
1429
1430        public void enable() {
1431            if (mAlarmManager == null) {
1432                mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
1433            }
1434
1435            if (mTimeoutIntent == null) {
1436                Intent intent = new Intent(ACTION_TIMEOUT, null);
1437                mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
1438
1439                mContext.registerReceiver(
1440                        new BroadcastReceiver() {
1441                            @Override
1442                            public void onReceive(Context context, Intent intent) {
1443                                sendMessage(WIFI_CHANGE_CMD_CHANGE_TIMEOUT);
1444                            }
1445                        }, new IntentFilter(ACTION_TIMEOUT));
1446            }
1447
1448            sendMessage(WIFI_CHANGE_CMD_ENABLE);
1449        }
1450
1451        public void disable() {
1452            sendMessage(WIFI_CHANGE_CMD_DISABLE);
1453        }
1454
1455        public void configure(WifiScanner.WifiChangeSettings settings) {
1456            sendMessage(WIFI_CHANGE_CMD_CONFIGURE, settings);
1457        }
1458
1459        class DefaultState extends State {
1460            @Override
1461            public void enter() {
1462                if (DBG) localLog("Entering IdleState");
1463            }
1464
1465            @Override
1466            public boolean processMessage(Message msg) {
1467                if (DBG) localLog("DefaultState state got " + msg);
1468                switch (msg.what) {
1469                    case WIFI_CHANGE_CMD_ENABLE :
1470                        transitionTo(mMovingState);
1471                        break;
1472                    case WIFI_CHANGE_CMD_DISABLE:
1473                        // nothing to do
1474                        break;
1475                    case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS:
1476                        // nothing to do
1477                        break;
1478                    case WIFI_CHANGE_CMD_CONFIGURE:
1479                        /* save configuration till we transition to moving state */
1480                        deferMessage(msg);
1481                        break;
1482                    default:
1483                        return NOT_HANDLED;
1484                }
1485                return HANDLED;
1486            }
1487        }
1488
1489        class StationaryState extends State {
1490            @Override
1491            public void enter() {
1492                if (DBG) localLog("Entering StationaryState");
1493                reportWifiStabilized(mCurrentBssids);
1494            }
1495
1496            @Override
1497            public boolean processMessage(Message msg) {
1498                if (DBG) localLog("Stationary state got " + msg);
1499                switch (msg.what) {
1500                    case WIFI_CHANGE_CMD_ENABLE :
1501                        // do nothing
1502                        break;
1503                    case WIFI_CHANGE_CMD_CHANGE_DETECTED:
1504                        if (DBG) localLog("Got wifi change detected");
1505                        reportWifiChanged((ScanResult[])msg.obj);
1506                        transitionTo(mMovingState);
1507                        break;
1508                    case WIFI_CHANGE_CMD_DISABLE:
1509                        if (DBG) localLog("Got Disable Wifi Change");
1510                        mCurrentBssids = null;
1511                        removeScanRequest();
1512                        untrackSignificantWifiChange();
1513                        transitionTo(mDefaultState);
1514                        break;
1515                    case WIFI_CHANGE_CMD_CONFIGURE:
1516                        /* save configuration till we transition to moving state */
1517                        deferMessage(msg);
1518                        break;
1519                    default:
1520                        return NOT_HANDLED;
1521                }
1522                return HANDLED;
1523            }
1524        }
1525
1526        class MovingState extends State {
1527            boolean mWifiChangeDetected = false;
1528            boolean mScanResultsPending = false;
1529
1530            @Override
1531            public void enter() {
1532                if (DBG) localLog("Entering MovingState");
1533                issueFullScan();
1534            }
1535
1536            @Override
1537            public boolean processMessage(Message msg) {
1538                if (DBG) localLog("MovingState state got " + msg);
1539                switch (msg.what) {
1540                    case WIFI_CHANGE_CMD_ENABLE :
1541                        // do nothing
1542                        break;
1543                    case WIFI_CHANGE_CMD_DISABLE:
1544                        if (DBG) localLog("Got Disable Wifi Change");
1545                        mCurrentBssids = null;
1546                        removeScanRequest();
1547                        untrackSignificantWifiChange();
1548                        transitionTo(mDefaultState);
1549                        break;
1550                    case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS:
1551                        if (DBG) localLog("Got scan results");
1552                        if (mScanResultsPending) {
1553                            if (DBG) localLog("reconfiguring scan");
1554                            reconfigureScan((ScanData[])msg.obj,
1555                                    STATIONARY_SCAN_PERIOD_MS);
1556                            mWifiChangeDetected = false;
1557                            mAlarmManager.setExact(AlarmManager.RTC_WAKEUP,
1558                                    System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS,
1559                                    mTimeoutIntent);
1560                            mScanResultsPending = false;
1561                        }
1562                        break;
1563                    case WIFI_CHANGE_CMD_CONFIGURE:
1564                        if (DBG) localLog("Got configuration from app");
1565                        WifiScanner.WifiChangeSettings settings =
1566                                (WifiScanner.WifiChangeSettings) msg.obj;
1567                        reconfigureScan(settings);
1568                        mWifiChangeDetected = false;
1569                        long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs;
1570                        mAlarmManager.cancel(mTimeoutIntent);
1571                        mAlarmManager.setExact(AlarmManager.RTC_WAKEUP,
1572                                System.currentTimeMillis() + unchangedDelay,
1573                                mTimeoutIntent);
1574                        break;
1575                    case WIFI_CHANGE_CMD_CHANGE_DETECTED:
1576                        if (DBG) localLog("Change detected");
1577                        mAlarmManager.cancel(mTimeoutIntent);
1578                        reportWifiChanged((ScanResult[])msg.obj);
1579                        mWifiChangeDetected = true;
1580                        issueFullScan();
1581                        break;
1582                    case WIFI_CHANGE_CMD_CHANGE_TIMEOUT:
1583                        if (DBG) localLog("Got timeout event");
1584                        if (mWifiChangeDetected == false) {
1585                            transitionTo(mStationaryState);
1586                        }
1587                        break;
1588                    default:
1589                        return NOT_HANDLED;
1590                }
1591                return HANDLED;
1592            }
1593
1594            @Override
1595            public void exit() {
1596                mAlarmManager.cancel(mTimeoutIntent);
1597            }
1598
1599            void issueFullScan() {
1600                if (DBG) localLog("Issuing full scan");
1601                ScanSettings settings = new ScanSettings();
1602                settings.band = WifiScanner.WIFI_BAND_BOTH;
1603                settings.periodInMs = MOVING_SCAN_PERIOD_MS;
1604                settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
1605                addScanRequest(settings);
1606                mScanResultsPending = true;
1607            }
1608
1609        }
1610
1611        void reconfigureScan(ScanData[] results, int period) {
1612            // find brightest APs and set them as sentinels
1613            if (results.length < MAX_APS_TO_TRACK) {
1614                localLog("too few APs (" + results.length + ") available to track wifi change");
1615                return;
1616            }
1617
1618            removeScanRequest();
1619
1620            // remove duplicate BSSIDs
1621            HashMap<String, ScanResult> bssidToScanResult = new HashMap<String, ScanResult>();
1622            for (ScanResult result : results[0].getResults()) {
1623                ScanResult saved = bssidToScanResult.get(result.BSSID);
1624                if (saved == null) {
1625                    bssidToScanResult.put(result.BSSID, result);
1626                } else if (saved.level > result.level) {
1627                    bssidToScanResult.put(result.BSSID, result);
1628                }
1629            }
1630
1631            // find brightest BSSIDs
1632            ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK];
1633            Collection<ScanResult> results2 = bssidToScanResult.values();
1634            for (ScanResult result : results2) {
1635                for (int j = 0; j < brightest.length; j++) {
1636                    if (brightest[j] == null
1637                            || (brightest[j].level < result.level)) {
1638                        for (int k = brightest.length; k > (j + 1); k--) {
1639                            brightest[k - 1] = brightest[k - 2];
1640                        }
1641                        brightest[j] = result;
1642                        break;
1643                    }
1644                }
1645            }
1646
1647            // Get channels to scan for
1648            ArrayList<Integer> channels = new ArrayList<Integer>();
1649            for (int i = 0; i < brightest.length; i++) {
1650                boolean found = false;
1651                for (int j = i + 1; j < brightest.length; j++) {
1652                    if (brightest[j].frequency == brightest[i].frequency) {
1653                        found = true;
1654                    }
1655                }
1656                if (!found) {
1657                    channels.add(brightest[i].frequency);
1658                }
1659            }
1660
1661            if (DBG) localLog("Found " + channels.size() + " channels");
1662
1663            // set scanning schedule
1664            ScanSettings settings = new ScanSettings();
1665            settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
1666            settings.channels = new ChannelSpec[channels.size()];
1667            for (int i = 0; i < channels.size(); i++) {
1668                settings.channels[i] = new ChannelSpec(channels.get(i));
1669            }
1670
1671            settings.periodInMs = period;
1672            addScanRequest(settings);
1673
1674            WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings();
1675            settings2.rssiSampleSize = 3;
1676            settings2.lostApSampleSize = 3;
1677            settings2.unchangedSampleSize = 3;
1678            settings2.minApsBreachingThreshold = 2;
1679            settings2.bssidInfos = new BssidInfo[brightest.length];
1680
1681            for (int i = 0; i < brightest.length; i++) {
1682                BssidInfo BssidInfo = new BssidInfo();
1683                BssidInfo.bssid = brightest[i].BSSID;
1684                int threshold = (100 + brightest[i].level) / 32 + 2;
1685                BssidInfo.low = brightest[i].level - threshold;
1686                BssidInfo.high = brightest[i].level + threshold;
1687                settings2.bssidInfos[i] = BssidInfo;
1688
1689                if (DBG) localLog("Setting bssid=" + BssidInfo.bssid + ", " +
1690                        "low=" + BssidInfo.low + ", high=" + BssidInfo.high);
1691            }
1692
1693            trackSignificantWifiChange(settings2);
1694            mCurrentBssids = brightest;
1695        }
1696
1697        void reconfigureScan(WifiScanner.WifiChangeSettings settings) {
1698
1699            if (settings.bssidInfos.length < MAX_APS_TO_TRACK) {
1700                localLog("too few APs (" + settings.bssidInfos.length
1701                        + ") available to track wifi change");
1702                return;
1703            }
1704
1705            if (DBG) localLog("Setting configuration specified by app");
1706
1707            mCurrentBssids = new ScanResult[settings.bssidInfos.length];
1708            HashSet<Integer> channels = new HashSet<Integer>();
1709
1710            for (int i = 0; i < settings.bssidInfos.length; i++) {
1711                ScanResult result = new ScanResult();
1712                result.BSSID = settings.bssidInfos[i].bssid;
1713                mCurrentBssids[i] = result;
1714                channels.add(settings.bssidInfos[i].frequencyHint);
1715            }
1716
1717            // cancel previous scan
1718            removeScanRequest();
1719
1720            // set new scanning schedule
1721            ScanSettings settings2 = new ScanSettings();
1722            settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
1723            settings2.channels = new ChannelSpec[channels.size()];
1724            int i = 0;
1725            for (Integer channel : channels) {
1726                settings2.channels[i++] = new ChannelSpec(channel);
1727            }
1728
1729            settings2.periodInMs = settings.periodInMs;
1730            addScanRequest(settings2);
1731
1732            // start tracking new APs
1733            trackSignificantWifiChange(settings);
1734        }
1735
1736        class ClientInfoLocal extends ClientInfo {
1737            ClientInfoLocal() {
1738                super(0, null, null);
1739            }
1740            @Override
1741            void deliverScanResults(int handler, ScanData results[]) {
1742                if (DBG) localLog("Delivering messages directly");
1743                sendMessage(WIFI_CHANGE_CMD_NEW_SCAN_RESULTS, 0, 0, results);
1744            }
1745            @Override
1746            void reportPeriodChanged(int handler, ScanSettings settings, int newPeriodInMs) {
1747                // nothing to do; no one is listening for this
1748            }
1749        }
1750
1751        @Override
1752        public void onChangesFound(ScanResult results[]) {
1753            sendMessage(WIFI_CHANGE_CMD_CHANGE_DETECTED, 0, 0, results);
1754        }
1755
1756        ClientInfo mClientInfo = new ClientInfoLocal();
1757        private static final int SCAN_COMMAND_ID = 1;
1758
1759        void addScanRequest(ScanSettings settings) {
1760            if (DBG) localLog("Starting scans");
1761            Message msg = Message.obtain();
1762            msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN;
1763            msg.arg2 = SCAN_COMMAND_ID;
1764            msg.obj = settings;
1765            mClientHandler.sendMessage(msg);
1766        }
1767
1768        void removeScanRequest() {
1769            if (DBG) localLog("Stopping scans");
1770            Message msg = Message.obtain();
1771            msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN;
1772            msg.arg2 = SCAN_COMMAND_ID;
1773            mClientHandler.sendMessage(msg);
1774        }
1775
1776        void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) {
1777            WifiNative.untrackSignificantWifiChange();
1778            WifiNative.trackSignificantWifiChange(settings, this);
1779        }
1780
1781        void untrackSignificantWifiChange() {
1782            WifiNative.untrackSignificantWifiChange();
1783        }
1784
1785    }
1786
1787    private static ChannelSpec mChannels[][];
1788
1789    private static void copyChannels(
1790            ChannelSpec channelSpec[], int offset, int channels[]) {
1791        for (int i = 0; i < channels.length; i++) {
1792            channelSpec[offset +i] = new ChannelSpec(channels[i]);
1793        }
1794    }
1795
1796    private static boolean initChannels() {
1797        if (mChannels != null) {
1798            /* already initialized */
1799            return true;
1800        }
1801
1802        int channels24[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
1803        if (channels24 == null) {
1804            loge("Could not get channels for 2.4 GHz");
1805            return false;
1806        }
1807
1808        int channels5[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
1809        if (channels5 == null) {
1810            loge("Could not get channels for 5 GHz");
1811            return false;
1812        }
1813
1814        int channelsDfs[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
1815        if (channelsDfs == null) {
1816            loge("Could not get channels for DFS");
1817            return false;
1818        }
1819
1820        mChannels = new ChannelSpec[8][];
1821
1822        mChannels[0] = new ChannelSpec[0];
1823
1824        mChannels[1] = new ChannelSpec[channels24.length];
1825        copyChannels(mChannels[1], 0, channels24);
1826
1827        mChannels[2] = new ChannelSpec[channels5.length];
1828        copyChannels(mChannels[2], 0, channels5);
1829
1830        mChannels[3] = new ChannelSpec[channels24.length + channels5.length];
1831        copyChannels(mChannels[3], 0, channels24);
1832        copyChannels(mChannels[3], channels24.length, channels5);
1833
1834        mChannels[4] = new ChannelSpec[channelsDfs.length];
1835        copyChannels(mChannels[4], 0, channelsDfs);
1836
1837        mChannels[5] = new ChannelSpec[channels24.length + channelsDfs.length];
1838        copyChannels(mChannels[5], 0, channels24);
1839        copyChannels(mChannels[5], channels24.length, channelsDfs);
1840
1841        mChannels[6] = new ChannelSpec[channels5.length + channelsDfs.length];
1842        copyChannels(mChannels[6], 0, channels5);
1843        copyChannels(mChannels[6], channels5.length, channelsDfs);
1844
1845        mChannels[7] = new ChannelSpec[
1846                channels24.length + channels5.length + channelsDfs.length];
1847        copyChannels(mChannels[7], 0, channels24);
1848        copyChannels(mChannels[7], channels24.length, channels5);
1849        copyChannels(mChannels[7], channels24.length + channels5.length, channelsDfs);
1850
1851        return true;
1852    }
1853
1854    private static ChannelSpec[] getChannelsForBand(int band) {
1855        initChannels();
1856
1857        if (band < WifiScanner.WIFI_BAND_24_GHZ || band > WifiScanner.WIFI_BAND_BOTH_WITH_DFS)
1858            /* invalid value for band */
1859            return mChannels[0];
1860        else
1861            return mChannels[band];
1862    }
1863
1864    private static boolean isDfs(int channel) {
1865        ChannelSpec[] dfsChannels = getChannelsForBand(WifiScanner
1866                .WIFI_BAND_5_GHZ_DFS_ONLY);
1867        for (int i = 0; i < dfsChannels.length; i++) {
1868            if (channel == dfsChannels[i].frequency) {
1869                return true;
1870            }
1871        }
1872        return false;
1873    }
1874
1875    private static int getBandFromChannels(ChannelSpec[] channels) {
1876        int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
1877        for (ChannelSpec channel : channels) {
1878            if (2400 <= channel.frequency && channel.frequency < 2500) {
1879                band |= WifiScanner.WIFI_BAND_24_GHZ;
1880            } else if ( isDfs(channel.frequency)) {
1881                band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
1882            } else if (5100 <= channel.frequency && channel.frequency < 6000) {
1883                band |= WifiScanner.WIFI_BAND_5_GHZ;
1884            }
1885        }
1886        return band;
1887    }
1888
1889    private static int getBandFromChannels(WifiNative.ChannelSettings[] channels) {
1890        int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
1891        for (WifiNative.ChannelSettings channel : channels) {
1892            if (channel != null) {
1893                if (2400 <= channel.frequency && channel.frequency < 2500) {
1894                    band |= WifiScanner.WIFI_BAND_24_GHZ;
1895                } else if ( isDfs(channel.frequency)) {
1896                    band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
1897                } else if (5100 <= channel.frequency && channel.frequency < 6000) {
1898                    band |= WifiScanner.WIFI_BAND_5_GHZ;
1899                }
1900            }
1901        }
1902        return band;
1903    }
1904
1905    @Override
1906    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1907        mStateMachine.dump(fd, pw, args);
1908    }
1909
1910    static String describe(ScanSettings scanSettings) {
1911        StringBuilder sb = new StringBuilder();
1912        sb.append("  band:").append(scanSettings.band);
1913        sb.append("  period:").append(scanSettings.periodInMs);
1914        sb.append("  reportEvents:").append(scanSettings.reportEvents);
1915        sb.append("  numBssidsPerScan:").append(scanSettings.numBssidsPerScan);
1916        sb.append("  maxScansToCache:").append(scanSettings.maxScansToCache).append("\n");
1917
1918        sb.append("  channels: ");
1919
1920        if (scanSettings.channels != null) {
1921            for (int i = 0; i < scanSettings.channels.length; i++) {
1922                sb.append(scanSettings.channels[i].frequency);
1923                sb.append(" ");
1924            }
1925        }
1926        sb.append("\n");
1927        return sb.toString();
1928    }
1929
1930}
1931