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