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 
WifiScannerFacade(FacadeManager manager)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 
getWifiScanResult(Integer listenerIndex)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 
WifiActionListener(String type, int idx, Bundle resultBundle, long startScanERT)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
onSuccess()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
onFailure(int reason, String description)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 
reportResult(ScanResult[] results, String type)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      */
genWifiScanListener()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      */
genBackgroundWifiScanListener()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 
WifiScanListener()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
onSuccess()185         public void onSuccess() {
186             mWAL.onSuccess();
187         }
188 
189         @Override
onFailure(int reason, String description)190         public void onFailure(int reason, String description) {
191             scanListeners.remove(mIndex);
192             mWAL.onFailure(reason, description);
193         }
194 
195         @Override
onPeriodChanged(int periodInMs)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
onFullResult(ScanResult fullScanResult)205         public void onFullResult(ScanResult fullScanResult) {
206             Log.d("onFullResult WifiScanListener " + mIndex);
207             mWAL.reportResult(new ScanResult[] {
208                     fullScanResult
209             }, "onFullResult");
210         }
211 
onResults(ScanData[] results)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      */
genWifiChangeListener()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 
ChangeListener()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
onSuccess()256         public void onSuccess() {
257             mWAL.onSuccess();
258         }
259 
260         @Override
onFailure(int reason, String description)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
onChanging(ScanResult[] results)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
onQuiescence(ScanResult[] results)282         public void onQuiescence(ScanResult[] results) { /* changes settled down */
283             mWAL.reportResult(results, "onQuiescence");
284         }
285     }
286 
genWifiBssidListener()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 
WifiBssidListener()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
onSuccess()314         public void onSuccess() {
315             mWAL.onSuccess();
316         }
317 
318         @Override
onFailure(int reason, String description)319         public void onFailure(int reason, String description) {
320             trackBssidListeners.remove(mIndex);
321             mWAL.onFailure(reason, description);
322         }
323 
324         @Override
onFound(ScanResult[] results)325         public void onFound(ScanResult[] results) {
326             mWAL.reportResult(results, "onFound");
327         }
328 
329         @Override
onLost(ScanResult[] results)330         public void onLost(ScanResult[] results) {
331             mWAL.reportResult(results, "onLost");
332         }
333     }
334 
parseScanSettings(JSONObject j)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 
parseBssidInfo(JSONArray jBssids)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")
wifiScannerStartBackgroundScan( @pcParametername = "scanSettings") JSONObject scanSettings)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")
wifiScannerGetScanResults()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")
wifiScannerStopBackgroundScan( @pcParametername = "listener") Integer listenerIndex)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")
wifiScannerStartScan( @pcParametername = "scanSettings") JSONObject scanSettings)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")
wifiScannerStopScan(@pcParametername = "listener") Integer listenerIndex)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.")
wifiScannerGetAvailableChannels( @pcParametername = "band") Integer band)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")
wifiScannerStartTrackingChange()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")
wifiScannerStopTrackingChange( @pcParametername = "listener") Integer listenerIndex)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.")
wifiScannerStartTrackingBssids( @pcParametername = "bssidInfos") JSONArray bssidInfos, @RpcParameter(name = "apLostThreshold") Integer apLostThreshold)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")
wifiScannerStopTrackingBssids( @pcParametername = "listener") Integer listenerIndex)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.")
wifiScannerToggleAlwaysAvailable( @pcParametername = "alwaysAvailable") @pcOptional Boolean alwaysAvailable)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.")
wifiScannerIsAlwaysAvailable()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")
wifiGetCurrentScanIndexes()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")
wifiScannerStartTrackingChangeWithSetting( @pcParametername = "trackSettings") JSONArray bssidSettings, @RpcParameter(name = "rssiSS") Integer rssiSS, @RpcParameter(name = "lostApSS") Integer lostApSS, @RpcParameter(name = "unchangedSS") Integer unchangedSS, @RpcParameter(name = "minApsBreachingThreshold") Integer minApsBreachingThreshold, @RpcParameter(name = "periodInMs") Integer periodInMs)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.")
wifiScannerShutdown()606     public void wifiScannerShutdown() {
607         this.shutdown();
608     }
609 
610     /**
611      * Stops all activity
612      */
613     @Override
shutdown()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