Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2017 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 
     17 package com.googlecode.android_scripting.facade.wifi;
     18 
     19 import android.app.Service;
     20 import android.content.Context;
     21 import android.net.wifi.ScanResult;
     22 import android.net.wifi.WifiScanner;
     23 import android.net.wifi.WifiScanner.BssidInfo;
     24 import android.net.wifi.WifiScanner.ChannelSpec;
     25 import android.net.wifi.WifiScanner.ScanData;
     26 import android.net.wifi.WifiScanner.ScanSettings;
     27 import android.os.Bundle;
     28 import android.os.SystemClock;
     29 import android.provider.Settings.Global;
     30 import android.provider.Settings.SettingNotFoundException;
     31 
     32 import com.googlecode.android_scripting.Log;
     33 import com.googlecode.android_scripting.MainThread;
     34 import com.googlecode.android_scripting.facade.EventFacade;
     35 import com.googlecode.android_scripting.facade.FacadeManager;
     36 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     37 import com.googlecode.android_scripting.rpc.Rpc;
     38 import com.googlecode.android_scripting.rpc.RpcOptional;
     39 import com.googlecode.android_scripting.rpc.RpcParameter;
     40 
     41 import org.json.JSONArray;
     42 import org.json.JSONException;
     43 import org.json.JSONObject;
     44 
     45 import java.util.Arrays;
     46 import java.util.Iterator;
     47 import java.util.List;
     48 import java.util.Set;
     49 import java.util.concurrent.Callable;
     50 import java.util.concurrent.ConcurrentHashMap;
     51 
     52 /**
     53  * WifiScanner functions.
     54  */
     55 public 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         if (j.has("type")) {
    367             result.type = j.getInt("type");
    368         }
    369         return result;
    370     }
    371 
    372     private BssidInfo[] parseBssidInfo(JSONArray jBssids) throws JSONException {
    373         BssidInfo[] bssids = new BssidInfo[jBssids.length()];
    374         for (int i = 0; i < bssids.length; i++) {
    375             JSONObject bi = (JSONObject) jBssids.get(i);
    376             BssidInfo bssidInfo = new BssidInfo();
    377             bssidInfo.bssid = bi.getString("BSSID");
    378             bssidInfo.high = bi.getInt("high");
    379             bssidInfo.low = bi.getInt("low");
    380             if (bi.has("frequencyHint")) {
    381                 bssidInfo.frequencyHint = bi.getInt("frequencyHint");
    382             }
    383             bssids[i] = bssidInfo;
    384         }
    385         return bssids;
    386     }
    387 
    388     /**
    389      * Starts periodic WifiScanner scan
    390      *
    391      * @param scanSettings
    392      * @return the id of the scan listener associated with this scan
    393      * @throws JSONException
    394      */
    395     @Rpc(description = "Starts a WifiScanner Background scan")
    396     public Integer wifiScannerStartBackgroundScan(
    397             @RpcParameter(name = "scanSettings") JSONObject scanSettings)
    398                     throws JSONException {
    399         ScanSettings ss = parseScanSettings(scanSettings);
    400         Log.d("startWifiScannerScan with " + ss.channels);
    401         WifiScanListener listener = genBackgroundWifiScanListener();
    402         mScan.startBackgroundScan(ss, listener);
    403         return listener.mIndex;
    404     }
    405 
    406     /**
    407      * Get currently available scan results on appropriate listeners
    408      *
    409      * @return true if all scan results were reported correctly
    410      * @throws JSONException
    411      */
    412     @Rpc(description = "Get currently available scan results on appropriate listeners")
    413     public Boolean wifiScannerGetScanResults() throws JSONException {
    414         mScan.getScanResults();
    415         return true;
    416     }
    417 
    418     /**
    419      * Stops a WifiScanner scan
    420      *
    421      * @param listenerIndex the id of the scan listener whose scan to stop
    422      * @throws Exception
    423      */
    424     @Rpc(description = "Stops an ongoing  WifiScanner Background scan")
    425     public void wifiScannerStopBackgroundScan(
    426             @RpcParameter(name = "listener") Integer listenerIndex)
    427                     throws Exception {
    428         if (!scanBackgroundListeners.containsKey(listenerIndex)) {
    429             throw new Exception("Background scan session " + listenerIndex + " does not exist");
    430         }
    431         WifiScanListener listener = scanBackgroundListeners.get(listenerIndex);
    432         Log.d("stopWifiScannerScan listener " + listener.mIndex);
    433         mScan.stopBackgroundScan(listener);
    434         wifiScannerResultList.remove(listenerIndex);
    435         scanBackgroundListeners.remove(listenerIndex);
    436     }
    437 
    438     /**
    439      * Starts periodic WifiScanner scan
    440      *
    441      * @param scanSettings
    442      * @return the id of the scan listener associated with this scan
    443      * @throws JSONException
    444      */
    445     @Rpc(description = "Starts a WifiScanner single scan")
    446     public Integer wifiScannerStartScan(
    447             @RpcParameter(name = "scanSettings") JSONObject scanSettings)
    448                     throws JSONException {
    449         ScanSettings ss = parseScanSettings(scanSettings);
    450         Log.d("startWifiScannerScan with " + ss.channels);
    451         WifiScanListener listener = genWifiScanListener();
    452         mScan.startScan(ss, listener);
    453         return listener.mIndex;
    454     }
    455 
    456     /**
    457      * Stops a WifiScanner scan
    458      *
    459      * @param listenerIndex the id of the scan listener whose scan to stop
    460      * @throws Exception
    461      */
    462     @Rpc(description = "Stops an ongoing  WifiScanner Single scan")
    463     public void wifiScannerStopScan(@RpcParameter(name = "listener") Integer listenerIndex)
    464             throws Exception {
    465         if (!scanListeners.containsKey(listenerIndex)) {
    466             throw new Exception("Single scan session " + listenerIndex + " does not exist");
    467         }
    468         WifiScanListener listener = scanListeners.get(listenerIndex);
    469         Log.d("stopWifiScannerScan listener " + listener.mIndex);
    470         mScan.stopScan(listener);
    471         wifiScannerResultList.remove(listener.mIndex);
    472         scanListeners.remove(listenerIndex);
    473     }
    474 
    475     /** RPC Methods */
    476     @Rpc(description = "Returns the channels covered by the specified band number.")
    477     public List<Integer> wifiScannerGetAvailableChannels(
    478             @RpcParameter(name = "band") Integer band) {
    479         return mScan.getAvailableChannels(band);
    480     }
    481 
    482     /**
    483      * Starts tracking wifi changes
    484      *
    485      * @return the id of the change listener associated with this track
    486      * @throws Exception
    487      */
    488     @Rpc(description = "Starts tracking wifi changes")
    489     public Integer wifiScannerStartTrackingChange() throws Exception {
    490         ChangeListener listener = genWifiChangeListener();
    491         mScan.startTrackingWifiChange(listener);
    492         return listener.mIndex;
    493     }
    494 
    495     /**
    496      * Stops tracking wifi changes
    497      *
    498      * @param listenerIndex the id of the change listener whose track to stop
    499      * @throws Exception
    500      */
    501     @Rpc(description = "Stops tracking wifi changes")
    502     public void wifiScannerStopTrackingChange(
    503             @RpcParameter(name = "listener") Integer listenerIndex) throws Exception {
    504         if (!trackChangeListeners.containsKey(listenerIndex)) {
    505             throw new Exception("Wifi change tracking session " + listenerIndex
    506                     + " does not exist");
    507         }
    508         ChangeListener listener = trackChangeListeners.get(listenerIndex);
    509         mScan.stopTrackingWifiChange(listener);
    510         trackChangeListeners.remove(listenerIndex);
    511     }
    512 
    513     /**
    514      * Starts tracking changes of the specified bssids.
    515      *
    516      * @param bssidInfos An array of json strings, each representing a BssidInfo object.
    517      * @param apLostThreshold
    518      * @return The index of the listener used to start the tracking.
    519      * @throws JSONException
    520      */
    521     @Rpc(description = "Starts tracking changes of the specified bssids.")
    522     public Integer wifiScannerStartTrackingBssids(
    523             @RpcParameter(name = "bssidInfos") JSONArray bssidInfos,
    524             @RpcParameter(name = "apLostThreshold") Integer apLostThreshold) throws JSONException {
    525         BssidInfo[] bssids = parseBssidInfo(bssidInfos);
    526         WifiBssidListener listener = genWifiBssidListener();
    527         mScan.startTrackingBssids(bssids, apLostThreshold, listener);
    528         return listener.mIndex;
    529     }
    530 
    531     /**
    532      * Stops tracking the list of APs associated with the input listener
    533      *
    534      * @param listenerIndex the id of the bssid listener whose track to stop
    535      * @throws Exception
    536      */
    537     @Rpc(description = "Stops tracking changes in the APs on the list")
    538     public void wifiScannerStopTrackingBssids(
    539             @RpcParameter(name = "listener") Integer listenerIndex) throws Exception {
    540         if (!trackBssidListeners.containsKey(listenerIndex)) {
    541             throw new Exception("Bssid tracking session " + listenerIndex + " does not exist");
    542         }
    543         WifiBssidListener listener = trackBssidListeners.get(listenerIndex);
    544         mScan.stopTrackingBssids(listener);
    545         trackBssidListeners.remove(listenerIndex);
    546     }
    547 
    548     @Rpc(description = "Toggle the 'WiFi scan always available' option. If an input is given, the "
    549             + "option is set to what the input boolean indicates.")
    550     public void wifiScannerToggleAlwaysAvailable(
    551             @RpcParameter(name = "alwaysAvailable") @RpcOptional Boolean alwaysAvailable)
    552                     throws SettingNotFoundException {
    553         int new_state = 0;
    554         if (alwaysAvailable == null) {
    555             int current_state = Global.getInt(mService.getContentResolver(),
    556                     Global.WIFI_SCAN_ALWAYS_AVAILABLE);
    557             new_state = current_state ^ 0x1;
    558         } else {
    559             new_state = alwaysAvailable ? 1 : 0;
    560         }
    561         Global.putInt(mService.getContentResolver(), Global.WIFI_SCAN_ALWAYS_AVAILABLE, new_state);
    562     }
    563 
    564     @Rpc(description = "Returns true if WiFi scan is always available, false otherwise.")
    565     public Boolean wifiScannerIsAlwaysAvailable() throws SettingNotFoundException {
    566         int current_state = Global.getInt(mService.getContentResolver(),
    567                 Global.WIFI_SCAN_ALWAYS_AVAILABLE);
    568         if (current_state == 1) {
    569             return true;
    570         }
    571         return false;
    572     }
    573 
    574     @Rpc(description = "Returns a list of mIndexes of existing listeners")
    575     public Set<Integer> wifiGetCurrentScanIndexes() {
    576         return scanListeners.keySet();
    577     }
    578 
    579     /**
    580      * Starts tracking wifi changes
    581      *
    582      * @return the id of the change listener associated with this track
    583      * @throws Exception
    584      */
    585     @Rpc(description = "Starts tracking wifi changes with track settings")
    586     public Integer wifiScannerStartTrackingChangeWithSetting(
    587             @RpcParameter(name = "trackSettings") JSONArray bssidSettings,
    588             @RpcParameter(name = "rssiSS") Integer rssiSS,
    589             @RpcParameter(name = "lostApSS") Integer lostApSS,
    590             @RpcParameter(name = "unchangedSS") Integer unchangedSS,
    591             @RpcParameter(name = "minApsBreachingThreshold") Integer minApsBreachingThreshold,
    592             @RpcParameter(name = "periodInMs") Integer periodInMs) throws Exception {
    593         Log.d("starting change track with track settings");
    594         BssidInfo[] bssids = parseBssidInfo(bssidSettings);
    595         mScan.configureWifiChange(rssiSS, lostApSS, unchangedSS, minApsBreachingThreshold,
    596               periodInMs, bssids);
    597         ChangeListener listener = genWifiChangeListener();
    598         mScan.startTrackingWifiChange(listener);
    599         return listener.mIndex;
    600     }
    601 
    602     /**
    603      * Shuts down all activities associated with WifiScanner
    604      */
    605     @Rpc(description = "Shuts down all WifiScanner activities and remove listeners.")
    606     public void wifiScannerShutdown() {
    607         this.shutdown();
    608     }
    609 
    610     /**
    611      * Stops all activity
    612      */
    613     @Override
    614     public void shutdown() {
    615         try {
    616             if (!scanListeners.isEmpty()) {
    617                 Iterator<ConcurrentHashMap.Entry<Integer, WifiScanListener>> iter = scanListeners
    618                         .entrySet().iterator();
    619                 while (iter.hasNext()) {
    620                     ConcurrentHashMap.Entry<Integer, WifiScanListener> entry = iter.next();
    621                     this.wifiScannerStopScan(entry.getKey());
    622                 }
    623             }
    624             if (!scanBackgroundListeners.isEmpty()) {
    625                 Iterator<ConcurrentHashMap.Entry<Integer, WifiScanListener>> iter = scanBackgroundListeners
    626                         .entrySet().iterator();
    627                 while (iter.hasNext()) {
    628                     ConcurrentHashMap.Entry<Integer, WifiScanListener> entry = iter.next();
    629                     this.wifiScannerStopBackgroundScan(entry.getKey());
    630                 }
    631             }
    632             if (!trackChangeListeners.isEmpty()) {
    633                 Iterator<ConcurrentHashMap.Entry<Integer, ChangeListener>> iter = trackChangeListeners
    634                         .entrySet().iterator();
    635                 while (iter.hasNext()) {
    636                     ConcurrentHashMap.Entry<Integer, ChangeListener> entry = iter.next();
    637                     this.wifiScannerStopTrackingChange(entry.getKey());
    638                 }
    639             }
    640             if (!trackBssidListeners.isEmpty()) {
    641                 Iterator<ConcurrentHashMap.Entry<Integer, WifiBssidListener>> iter = trackBssidListeners
    642                         .entrySet().iterator();
    643                 while (iter.hasNext()) {
    644                     ConcurrentHashMap.Entry<Integer, WifiBssidListener> entry = iter.next();
    645                     this.wifiScannerStopTrackingBssids(entry.getKey());
    646                 }
    647             }
    648         } catch (Exception e) {
    649             Log.e("Shutdown failed: " + e.toString());
    650         }
    651     }
    652 }
    653