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