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