1/*
2 * Copyright (C) 2014 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.googlecode.android_scripting.facade.wifi;
18
19import java.util.Arrays;
20import java.util.Iterator;
21import java.util.List;
22import java.util.Set;
23import java.util.concurrent.Callable;
24import java.util.concurrent.ConcurrentHashMap;
25
26import org.json.JSONArray;
27import org.json.JSONException;
28import org.json.JSONObject;
29
30import com.googlecode.android_scripting.Log;
31import com.googlecode.android_scripting.MainThread;
32import com.googlecode.android_scripting.facade.EventFacade;
33import com.googlecode.android_scripting.facade.FacadeManager;
34import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
35import com.googlecode.android_scripting.rpc.Rpc;
36import com.googlecode.android_scripting.rpc.RpcOptional;
37import com.googlecode.android_scripting.rpc.RpcParameter;
38
39import android.app.Service;
40import android.content.Context;
41import android.net.wifi.ScanResult;
42import android.net.wifi.WifiScanner;
43import android.net.wifi.WifiScanner.BssidInfo;
44import android.net.wifi.WifiScanner.ChannelSpec;
45import android.net.wifi.WifiScanner.ScanData;
46import android.net.wifi.WifiScanner.ScanSettings;
47import android.os.Bundle;
48import android.os.SystemClock;
49import android.provider.Settings.Global;
50import android.provider.Settings.SettingNotFoundException;
51
52/**
53 * WifiScanner functions.
54 */
55public class WifiScannerFacade extends RpcReceiver {
56    private final Service mService;
57    private final EventFacade mEventFacade;
58    private final WifiScanner mScan;
59    // These counters are just for indexing;
60    // they do not represent the total number of listeners
61    private static int WifiScanListenerCnt;
62    private static int WifiChangeListenerCnt;
63    private static int WifiBssidListenerCnt;
64    private final ConcurrentHashMap<Integer, WifiScanListener> scanListeners;
65    private final ConcurrentHashMap<Integer, WifiScanListener> scanBackgroundListeners;
66    private final ConcurrentHashMap<Integer, ChangeListener> trackChangeListeners;
67    private final ConcurrentHashMap<Integer, WifiBssidListener> trackBssidListeners;
68    private static ConcurrentHashMap<Integer, ScanResult[]> wifiScannerResultList;
69    private static ConcurrentHashMap<Integer, ScanData[]> wifiScannerDataList;
70
71    public WifiScannerFacade(FacadeManager manager) {
72        super(manager);
73        mService = manager.getService();
74        mScan = (WifiScanner) mService.getSystemService(Context.WIFI_SCANNING_SERVICE);
75        mEventFacade = manager.getReceiver(EventFacade.class);
76        scanListeners = new ConcurrentHashMap<Integer, WifiScanListener>();
77        scanBackgroundListeners = new ConcurrentHashMap<Integer, WifiScanListener>();
78        trackChangeListeners = new ConcurrentHashMap<Integer, ChangeListener>();
79        trackBssidListeners = new ConcurrentHashMap<Integer, WifiBssidListener>();
80        wifiScannerResultList = new ConcurrentHashMap<Integer, ScanResult[]>();
81        wifiScannerDataList = new ConcurrentHashMap<Integer, ScanData[]>();
82    }
83
84    public static List<ScanResult> getWifiScanResult(Integer listenerIndex) {
85        ScanResult[] sr = wifiScannerResultList.get(listenerIndex);
86        return Arrays.asList(sr);
87    }
88
89    private class WifiActionListener implements WifiScanner.ActionListener {
90        private final Bundle mResults;
91        public int mIndex;
92        protected String mEventType;
93        private long startScanElapsedRealTime;
94
95        public WifiActionListener(String type, int idx, Bundle resultBundle, long startScanERT) {
96            this.mIndex = idx;
97            this.mEventType = type;
98            this.mResults = resultBundle;
99            this.startScanElapsedRealTime = startScanERT;
100        }
101
102        @Override
103        public void onSuccess() {
104            Log.d("onSuccess " + mEventType + " " + mIndex);
105            mResults.putString("Type", "onSuccess");
106            mResults.putInt("Index", mIndex);
107            mResults.putLong("ScanElapsedRealtime", startScanElapsedRealTime);
108            mEventFacade.postEvent(mEventType + mIndex + "onSuccess", mResults.clone());
109            mResults.clear();
110        }
111
112        @Override
113        public void onFailure(int reason, String description) {
114            Log.d("onFailure " + mEventType + " " + mIndex);
115            mResults.putString("Type", "onFailure");
116            mResults.putInt("Index", mIndex);
117            mResults.putInt("Reason", reason);
118            mResults.putString("Description", description);
119            mEventFacade.postEvent(mEventType + mIndex + "onFailure", mResults.clone());
120            mResults.clear();
121        }
122
123        public void reportResult(ScanResult[] results, String type) {
124            Log.d("reportResult " + mEventType + " " + mIndex);
125            mResults.putInt("Index", mIndex);
126            mResults.putLong("ResultElapsedRealtime", SystemClock.elapsedRealtime());
127            mResults.putString("Type", type);
128            mResults.putParcelableArray("Results", results);
129            mEventFacade.postEvent(mEventType + mIndex + type, mResults.clone());
130            mResults.clear();
131        }
132    }
133
134    /**
135     * Constructs a wifiScanListener obj and returns it
136     *
137     * @return WifiScanListener
138     */
139    private WifiScanListener genWifiScanListener() {
140        WifiScanListener mWifiScannerListener = MainThread.run(mService,
141                new Callable<WifiScanListener>() {
142                    @Override
143                    public WifiScanListener call() throws Exception {
144                        return new WifiScanListener();
145                    }
146                });
147        scanListeners.put(mWifiScannerListener.mIndex, mWifiScannerListener);
148        return mWifiScannerListener;
149    }
150
151    /**
152     * Constructs a wifiScanListener obj for background scan and returns it
153     *
154     * @return WifiScanListener
155     */
156    private WifiScanListener genBackgroundWifiScanListener() {
157        WifiScanListener mWifiScannerListener = MainThread.run(mService,
158                new Callable<WifiScanListener>() {
159                    @Override
160                    public WifiScanListener call() throws Exception {
161                        return new WifiScanListener();
162                    }
163                });
164        scanBackgroundListeners.put(mWifiScannerListener.mIndex, mWifiScannerListener);
165        return mWifiScannerListener;
166    }
167
168    private class WifiScanListener implements WifiScanner.ScanListener {
169        private static final String mEventType = "WifiScannerScan";
170        protected final Bundle mScanResults;
171        protected final Bundle mScanData;
172        private final WifiActionListener mWAL;
173        public int mIndex;
174
175        public WifiScanListener() {
176            mScanResults = new Bundle();
177            mScanData = new Bundle();
178            WifiScanListenerCnt += 1;
179            mIndex = WifiScanListenerCnt;
180            mWAL = new WifiActionListener(mEventType, mIndex, mScanResults,
181                    SystemClock.elapsedRealtime());
182        }
183
184        @Override
185        public void onSuccess() {
186            mWAL.onSuccess();
187        }
188
189        @Override
190        public void onFailure(int reason, String description) {
191            scanListeners.remove(mIndex);
192            mWAL.onFailure(reason, description);
193        }
194
195        @Override
196        public void onPeriodChanged(int periodInMs) {
197            Log.d("onPeriodChanged " + mEventType + " " + mIndex);
198            mScanResults.putString("Type", "onPeriodChanged");
199            mScanResults.putInt("NewPeriod", periodInMs);
200            mEventFacade.postEvent(mEventType + mIndex, mScanResults.clone());
201            mScanResults.clear();
202        }
203
204        @Override
205        public void onFullResult(ScanResult fullScanResult) {
206            Log.d("onFullResult WifiScanListener " + mIndex);
207            mWAL.reportResult(new ScanResult[] {
208                    fullScanResult
209            }, "onFullResult");
210        }
211
212        public void onResults(ScanData[] results) {
213            Log.d("onResult WifiScanListener " + mIndex);
214            wifiScannerDataList.put(mIndex, results);
215            mScanData.putInt("Index", mIndex);
216            mScanData.putLong("ResultElapsedRealtime", SystemClock.elapsedRealtime());
217            mScanData.putString("Type", "onResults");
218            mScanData.putParcelableArray("Results", results);
219            mEventFacade.postEvent(mEventType + mIndex + "onResults", mScanData.clone());
220            mScanData.clear();
221        }
222    }
223
224    /**
225     * Constructs a ChangeListener obj and returns it
226     *
227     * @return ChangeListener
228     */
229    private ChangeListener genWifiChangeListener() {
230        ChangeListener mWifiChangeListener = MainThread.run(mService,
231                new Callable<ChangeListener>() {
232                    @Override
233                    public ChangeListener call() throws Exception {
234                        return new ChangeListener();
235                    }
236                });
237        trackChangeListeners.put(mWifiChangeListener.mIndex, mWifiChangeListener);
238        return mWifiChangeListener;
239    }
240
241    private class ChangeListener implements WifiScanner.WifiChangeListener {
242        private static final String mEventType = "WifiScannerChange";
243        protected final Bundle mResults;
244        private final WifiActionListener mWAL;
245        public int mIndex;
246
247        public ChangeListener() {
248            mResults = new Bundle();
249            WifiChangeListenerCnt += 1;
250            mIndex = WifiChangeListenerCnt;
251            mWAL = new WifiActionListener(mEventType, mIndex, mResults,
252                    SystemClock.elapsedRealtime());
253        }
254
255        @Override
256        public void onSuccess() {
257            mWAL.onSuccess();
258        }
259
260        @Override
261        public void onFailure(int reason, String description) {
262            trackChangeListeners.remove(mIndex);
263            mWAL.onFailure(reason, description);
264        }
265
266        /**
267         * indicates that changes were detected in wifi environment
268         *
269         * @param results indicate the access points that exhibited change
270         */
271        @Override
272        public void onChanging(ScanResult[] results) { /* changes are found */
273            mWAL.reportResult(results, "onChanging");
274        }
275
276        /**
277         * indicates that no wifi changes are being detected for a while
278         *
279         * @param results indicate the access points that are bing monitored for change
280         */
281        @Override
282        public void onQuiescence(ScanResult[] results) { /* changes settled down */
283            mWAL.reportResult(results, "onQuiescence");
284        }
285    }
286
287    private WifiBssidListener genWifiBssidListener() {
288        WifiBssidListener mWifiBssidListener = MainThread.run(mService,
289                new Callable<WifiBssidListener>() {
290                    @Override
291                    public WifiBssidListener call() throws Exception {
292                        return new WifiBssidListener();
293                    }
294                });
295        trackBssidListeners.put(mWifiBssidListener.mIndex, mWifiBssidListener);
296        return mWifiBssidListener;
297    }
298
299    private class WifiBssidListener implements WifiScanner.BssidListener {
300        private static final String mEventType = "WifiScannerBssid";
301        protected final Bundle mResults;
302        private final WifiActionListener mWAL;
303        public int mIndex;
304
305        public WifiBssidListener() {
306            mResults = new Bundle();
307            WifiBssidListenerCnt += 1;
308            mIndex = WifiBssidListenerCnt;
309            mWAL = new WifiActionListener(mEventType, mIndex, mResults,
310                    SystemClock.elapsedRealtime());
311        }
312
313        @Override
314        public void onSuccess() {
315            mWAL.onSuccess();
316        }
317
318        @Override
319        public void onFailure(int reason, String description) {
320            trackBssidListeners.remove(mIndex);
321            mWAL.onFailure(reason, description);
322        }
323
324        @Override
325        public void onFound(ScanResult[] results) {
326            mWAL.reportResult(results, "onFound");
327        }
328
329        @Override
330        public void onLost(ScanResult[] results) {
331            mWAL.reportResult(results, "onLost");
332        }
333    }
334
335    private ScanSettings parseScanSettings(JSONObject j) throws JSONException {
336        if (j == null) {
337            return null;
338        }
339        ScanSettings result = new ScanSettings();
340        if (j.has("band")) {
341            result.band = j.optInt("band");
342        }
343        if (j.has("channels")) {
344            JSONArray chs = j.getJSONArray("channels");
345            ChannelSpec[] channels = new ChannelSpec[chs.length()];
346            for (int i = 0; i < channels.length; i++) {
347                channels[i] = new ChannelSpec(chs.getInt(i));
348            }
349            result.channels = channels;
350        }
351        if (j.has("maxScansToCache")) {
352            result.maxScansToCache = j.getInt("maxScansToCache");
353        }
354        /* periodInMs and reportEvents are required */
355        result.periodInMs = j.getInt("periodInMs");
356        if (j.has("maxPeriodInMs")) {
357            result.maxPeriodInMs = j.getInt("maxPeriodInMs");
358        }
359        if (j.has("stepCount")) {
360            result.stepCount = j.getInt("stepCount");
361        }
362        result.reportEvents = j.getInt("reportEvents");
363        if (j.has("numBssidsPerScan")) {
364            result.numBssidsPerScan = j.getInt("numBssidsPerScan");
365        }
366        return result;
367    }
368
369    private BssidInfo[] parseBssidInfo(JSONArray jBssids) throws JSONException {
370        BssidInfo[] bssids = new BssidInfo[jBssids.length()];
371        for (int i = 0; i < bssids.length; i++) {
372            JSONObject bi = (JSONObject) jBssids.get(i);
373            BssidInfo bssidInfo = new BssidInfo();
374            bssidInfo.bssid = bi.getString("BSSID");
375            bssidInfo.high = bi.getInt("high");
376            bssidInfo.low = bi.getInt("low");
377            if (bi.has("frequencyHint")) {
378                bssidInfo.frequencyHint = bi.getInt("frequencyHint");
379            }
380            bssids[i] = bssidInfo;
381        }
382        return bssids;
383    }
384
385    /**
386     * Starts periodic WifiScanner scan
387     *
388     * @param scanSettings
389     * @return the id of the scan listener associated with this scan
390     * @throws JSONException
391     */
392    @Rpc(description = "Starts a WifiScanner Background scan")
393    public Integer wifiScannerStartBackgroundScan(
394            @RpcParameter(name = "scanSettings") JSONObject scanSettings)
395                    throws JSONException {
396        ScanSettings ss = parseScanSettings(scanSettings);
397        Log.d("startWifiScannerScan with " + ss.channels);
398        WifiScanListener listener = genBackgroundWifiScanListener();
399        mScan.startBackgroundScan(ss, listener);
400        return listener.mIndex;
401    }
402
403    /**
404     * Get currently available scan results on appropriate listeners
405     *
406     * @return true if all scan results were reported correctly
407     * @throws JSONException
408     */
409    @Rpc(description = "Get currently available scan results on appropriate listeners")
410    public Boolean wifiScannerGetScanResults() throws JSONException {
411        mScan.getScanResults();
412        return true;
413    }
414
415    /**
416     * Stops a WifiScanner scan
417     *
418     * @param listenerIndex the id of the scan listener whose scan to stop
419     * @throws Exception
420     */
421    @Rpc(description = "Stops an ongoing  WifiScanner Background scan")
422    public void wifiScannerStopBackgroundScan(
423            @RpcParameter(name = "listener") Integer listenerIndex)
424                    throws Exception {
425        if (!scanBackgroundListeners.containsKey(listenerIndex)) {
426            throw new Exception("Background scan session " + listenerIndex + " does not exist");
427        }
428        WifiScanListener listener = scanBackgroundListeners.get(listenerIndex);
429        Log.d("stopWifiScannerScan listener " + listener.mIndex);
430        mScan.stopBackgroundScan(listener);
431        wifiScannerResultList.remove(listenerIndex);
432        scanBackgroundListeners.remove(listenerIndex);
433    }
434
435    /**
436     * Starts periodic WifiScanner scan
437     *
438     * @param scanSettings
439     * @return the id of the scan listener associated with this scan
440     * @throws JSONException
441     */
442    @Rpc(description = "Starts a WifiScanner single scan")
443    public Integer wifiScannerStartScan(
444            @RpcParameter(name = "scanSettings") JSONObject scanSettings)
445                    throws JSONException {
446        ScanSettings ss = parseScanSettings(scanSettings);
447        Log.d("startWifiScannerScan with " + ss.channels);
448        WifiScanListener listener = genWifiScanListener();
449        mScan.startScan(ss, listener);
450        return listener.mIndex;
451    }
452
453    /**
454     * Stops a WifiScanner scan
455     *
456     * @param listenerIndex the id of the scan listener whose scan to stop
457     * @throws Exception
458     */
459    @Rpc(description = "Stops an ongoing  WifiScanner Single scan")
460    public void wifiScannerStopScan(@RpcParameter(name = "listener") Integer listenerIndex)
461            throws Exception {
462        if (!scanListeners.containsKey(listenerIndex)) {
463            throw new Exception("Single scan session " + listenerIndex + " does not exist");
464        }
465        WifiScanListener listener = scanListeners.get(listenerIndex);
466        Log.d("stopWifiScannerScan listener " + listener.mIndex);
467        mScan.stopScan(listener);
468        wifiScannerResultList.remove(listener.mIndex);
469        scanListeners.remove(listenerIndex);
470    }
471
472    /** RPC Methods */
473    @Rpc(description = "Returns the channels covered by the specified band number.")
474    public List<Integer> wifiScannerGetAvailableChannels(
475            @RpcParameter(name = "band") Integer band) {
476        return mScan.getAvailableChannels(band);
477    }
478
479    /**
480     * Starts tracking wifi changes
481     *
482     * @return the id of the change listener associated with this track
483     * @throws Exception
484     */
485    @Rpc(description = "Starts tracking wifi changes")
486    public Integer wifiScannerStartTrackingChange() throws Exception {
487        ChangeListener listener = genWifiChangeListener();
488        mScan.startTrackingWifiChange(listener);
489        return listener.mIndex;
490    }
491
492    /**
493     * Stops tracking wifi changes
494     *
495     * @param listenerIndex the id of the change listener whose track to stop
496     * @throws Exception
497     */
498    @Rpc(description = "Stops tracking wifi changes")
499    public void wifiScannerStopTrackingChange(
500            @RpcParameter(name = "listener") Integer listenerIndex) throws Exception {
501        if (!trackChangeListeners.containsKey(listenerIndex)) {
502            throw new Exception("Wifi change tracking session " + listenerIndex
503                    + " does not exist");
504        }
505        ChangeListener listener = trackChangeListeners.get(listenerIndex);
506        mScan.stopTrackingWifiChange(listener);
507        trackChangeListeners.remove(listenerIndex);
508    }
509
510    /**
511     * Starts tracking changes of the specified bssids.
512     *
513     * @param bssidInfos An array of json strings, each representing a BssidInfo object.
514     * @param apLostThreshold
515     * @return The index of the listener used to start the tracking.
516     * @throws JSONException
517     */
518    @Rpc(description = "Starts tracking changes of the specified bssids.")
519    public Integer wifiScannerStartTrackingBssids(
520            @RpcParameter(name = "bssidInfos") JSONArray bssidInfos,
521            @RpcParameter(name = "apLostThreshold") Integer apLostThreshold) throws JSONException {
522        BssidInfo[] bssids = parseBssidInfo(bssidInfos);
523        WifiBssidListener listener = genWifiBssidListener();
524        mScan.startTrackingBssids(bssids, apLostThreshold, listener);
525        return listener.mIndex;
526    }
527
528    /**
529     * Stops tracking the list of APs associated with the input listener
530     *
531     * @param listenerIndex the id of the bssid listener whose track to stop
532     * @throws Exception
533     */
534    @Rpc(description = "Stops tracking changes in the APs on the list")
535    public void wifiScannerStopTrackingBssids(
536            @RpcParameter(name = "listener") Integer listenerIndex) throws Exception {
537        if (!trackBssidListeners.containsKey(listenerIndex)) {
538            throw new Exception("Bssid tracking session " + listenerIndex + " does not exist");
539        }
540        WifiBssidListener listener = trackBssidListeners.get(listenerIndex);
541        mScan.stopTrackingBssids(listener);
542        trackBssidListeners.remove(listenerIndex);
543    }
544
545    @Rpc(description = "Toggle the 'WiFi scan always available' option. If an input is given, the "
546            + "option is set to what the input boolean indicates.")
547    public void wifiScannerToggleAlwaysAvailable(
548            @RpcParameter(name = "alwaysAvailable") @RpcOptional Boolean alwaysAvailable)
549                    throws SettingNotFoundException {
550        int new_state = 0;
551        if (alwaysAvailable == null) {
552            int current_state = Global.getInt(mService.getContentResolver(),
553                    Global.WIFI_SCAN_ALWAYS_AVAILABLE);
554            new_state = current_state ^ 0x1;
555        } else {
556            new_state = alwaysAvailable ? 1 : 0;
557        }
558        Global.putInt(mService.getContentResolver(), Global.WIFI_SCAN_ALWAYS_AVAILABLE, new_state);
559    }
560
561    @Rpc(description = "Returns true if WiFi scan is always available, false otherwise.")
562    public Boolean wifiScannerIsAlwaysAvailable() throws SettingNotFoundException {
563        int current_state = Global.getInt(mService.getContentResolver(),
564                Global.WIFI_SCAN_ALWAYS_AVAILABLE);
565        if (current_state == 1) {
566            return true;
567        }
568        return false;
569    }
570
571    @Rpc(description = "Returns a list of mIndexes of existing listeners")
572    public Set<Integer> wifiGetCurrentScanIndexes() {
573        return scanListeners.keySet();
574    }
575
576    /**
577     * Starts tracking wifi changes
578     *
579     * @return the id of the change listener associated with this track
580     * @throws Exception
581     */
582    @Rpc(description = "Starts tracking wifi changes with track settings")
583    public Integer wifiScannerStartTrackingChangeWithSetting(
584            @RpcParameter(name = "trackSettings") JSONArray bssidSettings,
585            @RpcParameter(name = "rssiSS") Integer rssiSS,
586            @RpcParameter(name = "lostApSS") Integer lostApSS,
587            @RpcParameter(name = "unchangedSS") Integer unchangedSS,
588            @RpcParameter(name = "minApsBreachingThreshold") Integer minApsBreachingThreshold,
589            @RpcParameter(name = "periodInMs") Integer periodInMs) throws Exception {
590        Log.d("starting change track with track settings");
591        BssidInfo[] bssids = parseBssidInfo(bssidSettings);
592        mScan.configureWifiChange(rssiSS, lostApSS, unchangedSS, minApsBreachingThreshold,
593              periodInMs, bssids);
594        ChangeListener listener = genWifiChangeListener();
595        mScan.startTrackingWifiChange(listener);
596        return listener.mIndex;
597    }
598
599    /**
600     * Shuts down all activities associated with WifiScanner
601     */
602    @Rpc(description = "Shuts down all WifiScanner activities and remove listeners.")
603    public void wifiScannerShutdown() {
604        this.shutdown();
605    }
606
607    /**
608     * Stops all activity
609     */
610    @Override
611    public void shutdown() {
612        try {
613            if (!scanListeners.isEmpty()) {
614                Iterator<ConcurrentHashMap.Entry<Integer, WifiScanListener>> iter = scanListeners
615                        .entrySet().iterator();
616                while (iter.hasNext()) {
617                    ConcurrentHashMap.Entry<Integer, WifiScanListener> entry = iter.next();
618                    this.wifiScannerStopScan(entry.getKey());
619                }
620            }
621            if (!scanBackgroundListeners.isEmpty()) {
622                Iterator<ConcurrentHashMap.Entry<Integer, WifiScanListener>> iter = scanBackgroundListeners
623                        .entrySet().iterator();
624                while (iter.hasNext()) {
625                    ConcurrentHashMap.Entry<Integer, WifiScanListener> entry = iter.next();
626                    this.wifiScannerStopBackgroundScan(entry.getKey());
627                }
628            }
629            if (!trackChangeListeners.isEmpty()) {
630                Iterator<ConcurrentHashMap.Entry<Integer, ChangeListener>> iter = trackChangeListeners
631                        .entrySet().iterator();
632                while (iter.hasNext()) {
633                    ConcurrentHashMap.Entry<Integer, ChangeListener> entry = iter.next();
634                    this.wifiScannerStopTrackingChange(entry.getKey());
635                }
636            }
637            if (!trackBssidListeners.isEmpty()) {
638                Iterator<ConcurrentHashMap.Entry<Integer, WifiBssidListener>> iter = trackBssidListeners
639                        .entrySet().iterator();
640                while (iter.hasNext()) {
641                    ConcurrentHashMap.Entry<Integer, WifiBssidListener> entry = iter.next();
642                    this.wifiScannerStopTrackingBssids(entry.getKey());
643                }
644            }
645        } catch (Exception e) {
646            Log.e("Shutdown failed: " + e.toString());
647        }
648    }
649}
650