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