WifiScanner.java revision f45acfe0960f1182ed9d38d2acd78188de25b720
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net.wifi; 18 19import android.annotation.SystemApi; 20import android.content.Context; 21import android.os.Bundle; 22import android.os.Handler; 23import android.os.HandlerThread; 24import android.os.Looper; 25import android.os.Message; 26import android.os.Messenger; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.RemoteException; 30import android.util.Log; 31import android.util.SparseArray; 32 33import com.android.internal.util.AsyncChannel; 34import com.android.internal.util.Protocol; 35 36import java.util.List; 37import java.util.concurrent.CountDownLatch; 38 39 40/** 41 * This class provides a way to scan the Wifi universe around the device 42 * Get an instance of this class by calling 43 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context 44 * .WIFI_SCANNING_SERVICE)}. 45 * @hide 46 */ 47@SystemApi 48public class WifiScanner { 49 50 /** no band specified; use channel list instead */ 51 public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */ 52 53 /** 2.4 GHz band */ 54 public static final int WIFI_BAND_24_GHZ = 1; /* 2.4 GHz band */ 55 /** 5 GHz band excluding DFS channels */ 56 public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */ 57 /** DFS channels from 5 GHz band only */ 58 public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */ 59 /** 5 GHz band including DFS channels */ 60 public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */ 61 /** Both 2.4 GHz band and 5 GHz band; no DFS channels */ 62 public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */ 63 /** Both 2.4 GHz band and 5 GHz band; with DFS channels */ 64 public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ 65 66 /** Minimum supported scanning period */ 67 public static final int MIN_SCAN_PERIOD_MS = 1000; /* minimum supported period */ 68 /** Maximum supported scanning period */ 69 public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ 70 71 /** No Error */ 72 public static final int REASON_SUCCEEDED = 0; 73 /** Unknown error */ 74 public static final int REASON_UNSPECIFIED = -1; 75 /** Invalid listener */ 76 public static final int REASON_INVALID_LISTENER = -2; 77 /** Invalid request */ 78 public static final int REASON_INVALID_REQUEST = -3; 79 /** Invalid request */ 80 public static final int REASON_NOT_AUTHORIZED = -4; 81 82 /** @hide */ 83 public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels"; 84 85 /** 86 * Generic action callback invocation interface 87 * @hide 88 */ 89 @SystemApi 90 public static interface ActionListener { 91 public void onSuccess(); 92 public void onFailure(int reason, String description); 93 } 94 95 /** 96 * gives you all the possible channels; channel is specified as an 97 * integer with frequency in MHz i.e. channel 1 is 2412 98 * @hide 99 */ 100 public List<Integer> getAvailableChannels(int band) { 101 try { 102 Bundle bundle = mService.getAvailableChannels(band); 103 return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA); 104 } catch (RemoteException e) { 105 return null; 106 } 107 } 108 109 /** 110 * provides channel specification for scanning 111 */ 112 public static class ChannelSpec { 113 /** 114 * channel frequency in MHz; for example channel 1 is specified as 2412 115 */ 116 public int frequency; 117 /** 118 * if true, scan this channel in passive fashion. 119 * This flag is ignored on DFS channel specification. 120 * @hide 121 */ 122 public boolean passive; /* ignored on DFS channels */ 123 /** 124 * how long to dwell on this channel 125 * @hide 126 */ 127 public int dwellTimeMS; /* not supported for now */ 128 129 /** 130 * default constructor for channel spec 131 */ 132 public ChannelSpec(int frequency) { 133 this.frequency = frequency; 134 passive = false; 135 dwellTimeMS = 0; 136 } 137 } 138 139 /** reports {@link ScanListener#onResults} when underlying buffers are full */ 140 public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; 141 /** reports {@link ScanListener#onResults} after each scan */ 142 public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; 143 /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */ 144 public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; 145 146 /** 147 * scan configuration parameters to be sent to {@link #startBackgroundScan} 148 */ 149 public static class ScanSettings implements Parcelable { 150 151 /** one of the WIFI_BAND values */ 152 public int band; 153 /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ 154 public ChannelSpec[] channels; 155 /** period of background scan; in millisecond, 0 => single shot scan */ 156 public int periodInMs; 157 /** must have a valid REPORT_EVENT value */ 158 public int reportEvents; 159 /** defines number of bssids to cache from each scan */ 160 public int numBssidsPerScan; 161 /** 162 * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL 163 * to wake up at fixed interval 164 */ 165 public int maxScansToCache; 166 167 /** Implement the Parcelable interface {@hide} */ 168 public int describeContents() { 169 return 0; 170 } 171 172 /** Implement the Parcelable interface {@hide} */ 173 public void writeToParcel(Parcel dest, int flags) { 174 dest.writeInt(band); 175 dest.writeInt(periodInMs); 176 dest.writeInt(reportEvents); 177 dest.writeInt(numBssidsPerScan); 178 dest.writeInt(maxScansToCache); 179 180 if (channels != null) { 181 dest.writeInt(channels.length); 182 183 for (int i = 0; i < channels.length; i++) { 184 dest.writeInt(channels[i].frequency); 185 dest.writeInt(channels[i].dwellTimeMS); 186 dest.writeInt(channels[i].passive ? 1 : 0); 187 } 188 } else { 189 dest.writeInt(0); 190 } 191 } 192 193 /** Implement the Parcelable interface {@hide} */ 194 public static final Creator<ScanSettings> CREATOR = 195 new Creator<ScanSettings>() { 196 public ScanSettings createFromParcel(Parcel in) { 197 198 ScanSettings settings = new ScanSettings(); 199 settings.band = in.readInt(); 200 settings.periodInMs = in.readInt(); 201 settings.reportEvents = in.readInt(); 202 settings.numBssidsPerScan = in.readInt(); 203 settings.maxScansToCache = in.readInt(); 204 int num_channels = in.readInt(); 205 settings.channels = new ChannelSpec[num_channels]; 206 for (int i = 0; i < num_channels; i++) { 207 int frequency = in.readInt(); 208 209 ChannelSpec spec = new ChannelSpec(frequency); 210 spec.dwellTimeMS = in.readInt(); 211 spec.passive = in.readInt() == 1; 212 settings.channels[i] = spec; 213 } 214 215 return settings; 216 } 217 218 public ScanSettings[] newArray(int size) { 219 return new ScanSettings[size]; 220 } 221 }; 222 223 } 224 225 /** 226 * all the information garnered from a single scan 227 */ 228 public static class ScanData implements Parcelable { 229 /** scan identifier */ 230 private int mId; 231 /** additional information about scan 232 * 0 => no special issues encountered in the scan 233 * non-zero => scan was truncated, so results may not be complete 234 */ 235 private int mFlags; 236 /** all scan results discovered in this scan, sorted by timestamp in ascending order */ 237 private ScanResult mResults[]; 238 239 ScanData() {} 240 241 public ScanData(int id, int flags, ScanResult[] results) { 242 mId = id; 243 mFlags = flags; 244 mResults = results; 245 } 246 247 public ScanData(ScanData s) { 248 mId = s.mId; 249 mFlags = s.mFlags; 250 mResults = new ScanResult[s.mResults.length]; 251 for (int i = 0; i < s.mResults.length; i++) { 252 ScanResult result = s.mResults[i]; 253 WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID); 254 ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "", 255 result.level, result.frequency, result.timestamp); 256 mResults[i] = newResult; 257 } 258 } 259 260 public int getId() { 261 return mId; 262 } 263 264 public int getFlags() { 265 return mFlags; 266 } 267 268 public ScanResult[] getResults() { 269 return mResults; 270 } 271 272 /** Implement the Parcelable interface {@hide} */ 273 public int describeContents() { 274 return 0; 275 } 276 277 /** Implement the Parcelable interface {@hide} */ 278 public void writeToParcel(Parcel dest, int flags) { 279 if (mResults != null) { 280 dest.writeInt(mId); 281 dest.writeInt(mFlags); 282 dest.writeInt(mResults.length); 283 for (int i = 0; i < mResults.length; i++) { 284 ScanResult result = mResults[i]; 285 result.writeToParcel(dest, flags); 286 } 287 } else { 288 dest.writeInt(0); 289 } 290 } 291 292 /** Implement the Parcelable interface {@hide} */ 293 public static final Creator<ScanData> CREATOR = 294 new Creator<ScanData>() { 295 public ScanData createFromParcel(Parcel in) { 296 int id = in.readInt(); 297 int flags = in.readInt(); 298 int n = in.readInt(); 299 ScanResult results[] = new ScanResult[n]; 300 for (int i = 0; i < n; i++) { 301 results[i] = ScanResult.CREATOR.createFromParcel(in); 302 } 303 return new ScanData(id, flags, results); 304 } 305 306 public ScanData[] newArray(int size) { 307 return new ScanData[size]; 308 } 309 }; 310 } 311 312 public static class ParcelableScanData implements Parcelable { 313 314 public ScanData mResults[]; 315 316 public ParcelableScanData(ScanData[] results) { 317 mResults = results; 318 } 319 320 public ScanData[] getResults() { 321 return mResults; 322 } 323 324 /** Implement the Parcelable interface {@hide} */ 325 public int describeContents() { 326 return 0; 327 } 328 329 /** Implement the Parcelable interface {@hide} */ 330 public void writeToParcel(Parcel dest, int flags) { 331 if (mResults != null) { 332 dest.writeInt(mResults.length); 333 for (int i = 0; i < mResults.length; i++) { 334 ScanData result = mResults[i]; 335 result.writeToParcel(dest, flags); 336 } 337 } else { 338 dest.writeInt(0); 339 } 340 } 341 342 /** Implement the Parcelable interface {@hide} */ 343 public static final Creator<ParcelableScanData> CREATOR = 344 new Creator<ParcelableScanData>() { 345 public ParcelableScanData createFromParcel(Parcel in) { 346 int n = in.readInt(); 347 ScanData results[] = new ScanData[n]; 348 for (int i = 0; i < n; i++) { 349 results[i] = ScanData.CREATOR.createFromParcel(in); 350 } 351 return new ParcelableScanData(results); 352 } 353 354 public ParcelableScanData[] newArray(int size) { 355 return new ParcelableScanData[size]; 356 } 357 }; 358 } 359 360 public static class ParcelableScanResults implements Parcelable { 361 362 public ScanResult mResults[]; 363 364 public ParcelableScanResults(ScanResult[] results) { 365 mResults = results; 366 } 367 368 public ScanResult[] getResults() { 369 return mResults; 370 } 371 372 /** Implement the Parcelable interface {@hide} */ 373 public int describeContents() { 374 return 0; 375 } 376 377 /** Implement the Parcelable interface {@hide} */ 378 public void writeToParcel(Parcel dest, int flags) { 379 if (mResults != null) { 380 dest.writeInt(mResults.length); 381 for (int i = 0; i < mResults.length; i++) { 382 ScanResult result = mResults[i]; 383 result.writeToParcel(dest, flags); 384 } 385 } else { 386 dest.writeInt(0); 387 } 388 } 389 390 /** Implement the Parcelable interface {@hide} */ 391 public static final Creator<ParcelableScanResults> CREATOR = 392 new Creator<ParcelableScanResults>() { 393 public ParcelableScanResults createFromParcel(Parcel in) { 394 int n = in.readInt(); 395 ScanResult results[] = new ScanResult[n]; 396 for (int i = 0; i < n; i++) { 397 results[i] = ScanResult.CREATOR.createFromParcel(in); 398 } 399 return new ParcelableScanResults(results); 400 } 401 402 public ParcelableScanResults[] newArray(int size) { 403 return new ParcelableScanResults[size]; 404 } 405 }; 406 } 407 408 /** 409 * interface to get scan events on; specify this on {@link #startBackgroundScan} or 410 * {@link #startScan} 411 */ 412 public interface ScanListener extends ActionListener { 413 /** 414 * Framework co-ordinates scans across multiple apps; so it may not give exactly the 415 * same period requested. If period of a scan is changed; it is reported by this event. 416 */ 417 public void onPeriodChanged(int periodInMs); 418 /** 419 * reports results retrieved from background scan and single shot scans 420 */ 421 public void onResults(ScanData[] results); 422 /** 423 * reports full scan result for each access point found in scan 424 */ 425 public void onFullResult(ScanResult fullScanResult); 426 } 427 428 /** start wifi scan in background 429 * @param settings specifies various parameters for the scan; for more information look at 430 * {@link ScanSettings} 431 * @param listener specifies the object to report events to. This object is also treated as a 432 * key for this scan, and must also be specified to cancel the scan. Multiple 433 * scans should also not share this object. 434 */ 435 public void startBackgroundScan(ScanSettings settings, ScanListener listener) { 436 validateChannel(); 437 sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings); 438 } 439 /** 440 * stop an ongoing wifi scan 441 * @param listener specifies which scan to cancel; must be same object as passed in {@link 442 * #startBackgroundScan} 443 */ 444 public void stopBackgroundScan(ScanListener listener) { 445 validateChannel(); 446 sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener)); 447 } 448 /** 449 * reports currently available scan results on appropriate listeners 450 * @return true if all scan results were reported correctly 451 */ 452 public boolean getScanResults() { 453 validateChannel(); 454 Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0); 455 return reply.what == CMD_OP_SUCCEEDED; 456 } 457 458 /** 459 * starts a single scan and reports results asynchronously 460 * @param settings specifies various parameters for the scan; for more information look at 461 * {@link ScanSettings} 462 * @param listener specifies the object to report events to. This object is also treated as a 463 * key for this scan, and must also be specified to cancel the scan. Multiple 464 * scans should also not share this object. 465 */ 466 public void startScan(ScanSettings settings, ScanListener listener) { 467 validateChannel(); 468 sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings); 469 } 470 471 /** 472 * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults() 473 * hasn't been called on the listener, ignored otherwise 474 * @param listener 475 */ 476 public void stopScan(ScanListener listener) { 477 validateChannel(); 478 sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener)); 479 } 480 481 /** specifies information about an access point of interest */ 482 public static class BssidInfo { 483 /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */ 484 public String bssid; 485 /** low signal strength threshold; more information at {@link ScanResult#level} */ 486 public int low; /* minimum RSSI */ 487 /** high signal threshold; more information at {@link ScanResult#level} */ 488 public int high; /* maximum RSSI */ 489 /** channel frequency (in KHz) where you may find this BSSID */ 490 public int frequencyHint; 491 } 492 493 /** @hide */ 494 @SystemApi 495 public static class WifiChangeSettings implements Parcelable { 496 public int rssiSampleSize; /* sample size for RSSI averaging */ 497 public int lostApSampleSize; /* samples to confirm AP's loss */ 498 public int unchangedSampleSize; /* samples to confirm no change */ 499 public int minApsBreachingThreshold; /* change threshold to trigger event */ 500 public int periodInMs; /* scan period in millisecond */ 501 public BssidInfo[] bssidInfos; 502 503 /** Implement the Parcelable interface {@hide} */ 504 public int describeContents() { 505 return 0; 506 } 507 508 /** Implement the Parcelable interface {@hide} */ 509 public void writeToParcel(Parcel dest, int flags) { 510 dest.writeInt(rssiSampleSize); 511 dest.writeInt(lostApSampleSize); 512 dest.writeInt(unchangedSampleSize); 513 dest.writeInt(minApsBreachingThreshold); 514 dest.writeInt(periodInMs); 515 if (bssidInfos != null) { 516 dest.writeInt(bssidInfos.length); 517 for (int i = 0; i < bssidInfos.length; i++) { 518 BssidInfo info = bssidInfos[i]; 519 dest.writeString(info.bssid); 520 dest.writeInt(info.low); 521 dest.writeInt(info.high); 522 dest.writeInt(info.frequencyHint); 523 } 524 } else { 525 dest.writeInt(0); 526 } 527 } 528 529 /** Implement the Parcelable interface {@hide} */ 530 public static final Creator<WifiChangeSettings> CREATOR = 531 new Creator<WifiChangeSettings>() { 532 public WifiChangeSettings createFromParcel(Parcel in) { 533 WifiChangeSettings settings = new WifiChangeSettings(); 534 settings.rssiSampleSize = in.readInt(); 535 settings.lostApSampleSize = in.readInt(); 536 settings.unchangedSampleSize = in.readInt(); 537 settings.minApsBreachingThreshold = in.readInt(); 538 settings.periodInMs = in.readInt(); 539 int len = in.readInt(); 540 settings.bssidInfos = new BssidInfo[len]; 541 for (int i = 0; i < len; i++) { 542 BssidInfo info = new BssidInfo(); 543 info.bssid = in.readString(); 544 info.low = in.readInt(); 545 info.high = in.readInt(); 546 info.frequencyHint = in.readInt(); 547 settings.bssidInfos[i] = info; 548 } 549 return settings; 550 } 551 552 public WifiChangeSettings[] newArray(int size) { 553 return new WifiChangeSettings[size]; 554 } 555 }; 556 557 } 558 559 /** configure WifiChange detection 560 * @param rssiSampleSize number of samples used for RSSI averaging 561 * @param lostApSampleSize number of samples to confirm an access point's loss 562 * @param unchangedSampleSize number of samples to confirm there are no changes 563 * @param minApsBreachingThreshold minimum number of access points that need to be 564 * out of range to detect WifiChange 565 * @param periodInMs indicates period of scan to find changes 566 * @param bssidInfos access points to watch 567 */ 568 public void configureWifiChange( 569 int rssiSampleSize, /* sample size for RSSI averaging */ 570 int lostApSampleSize, /* samples to confirm AP's loss */ 571 int unchangedSampleSize, /* samples to confirm no change */ 572 int minApsBreachingThreshold, /* change threshold to trigger event */ 573 int periodInMs, /* period of scan */ 574 BssidInfo[] bssidInfos /* signal thresholds to crosss */ 575 ) 576 { 577 validateChannel(); 578 579 WifiChangeSettings settings = new WifiChangeSettings(); 580 settings.rssiSampleSize = rssiSampleSize; 581 settings.lostApSampleSize = lostApSampleSize; 582 settings.unchangedSampleSize = unchangedSampleSize; 583 settings.minApsBreachingThreshold = minApsBreachingThreshold; 584 settings.periodInMs = periodInMs; 585 settings.bssidInfos = bssidInfos; 586 587 configureWifiChange(settings); 588 } 589 590 /** 591 * interface to get wifi change events on; use this on {@link #startTrackingWifiChange} 592 */ 593 public interface WifiChangeListener extends ActionListener { 594 /** indicates that changes were detected in wifi environment 595 * @param results indicate the access points that exhibited change 596 */ 597 public void onChanging(ScanResult[] results); /* changes are found */ 598 /** indicates that no wifi changes are being detected for a while 599 * @param results indicate the access points that are bing monitored for change 600 */ 601 public void onQuiescence(ScanResult[] results); /* changes settled down */ 602 } 603 604 /** 605 * track changes in wifi environment 606 * @param listener object to report events on; this object must be unique and must also be 607 * provided on {@link #stopTrackingWifiChange} 608 */ 609 public void startTrackingWifiChange(WifiChangeListener listener) { 610 validateChannel(); 611 sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener)); 612 } 613 614 /** 615 * stop tracking changes in wifi environment 616 * @param listener object that was provided to report events on {@link 617 * #stopTrackingWifiChange} 618 */ 619 public void stopTrackingWifiChange(WifiChangeListener listener) { 620 validateChannel(); 621 sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener)); 622 } 623 624 /** @hide */ 625 @SystemApi 626 public void configureWifiChange(WifiChangeSettings settings) { 627 validateChannel(); 628 sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); 629 } 630 631 /** interface to receive hotlist events on; use this on {@link #setHotlist} */ 632 public static interface BssidListener extends ActionListener { 633 /** indicates that access points were found by on going scans 634 * @param results list of scan results, one for each access point visible currently 635 */ 636 public void onFound(ScanResult[] results); 637 } 638 639 /** @hide */ 640 @SystemApi 641 public static class HotlistSettings implements Parcelable { 642 public BssidInfo[] bssidInfos; 643 public int apLostThreshold; 644 645 /** Implement the Parcelable interface {@hide} */ 646 public int describeContents() { 647 return 0; 648 } 649 650 /** Implement the Parcelable interface {@hide} */ 651 public void writeToParcel(Parcel dest, int flags) { 652 dest.writeInt(apLostThreshold); 653 654 if (bssidInfos != null) { 655 dest.writeInt(bssidInfos.length); 656 for (int i = 0; i < bssidInfos.length; i++) { 657 BssidInfo info = bssidInfos[i]; 658 dest.writeString(info.bssid); 659 dest.writeInt(info.low); 660 dest.writeInt(info.high); 661 dest.writeInt(info.frequencyHint); 662 } 663 } else { 664 dest.writeInt(0); 665 } 666 } 667 668 /** Implement the Parcelable interface {@hide} */ 669 public static final Creator<HotlistSettings> CREATOR = 670 new Creator<HotlistSettings>() { 671 public HotlistSettings createFromParcel(Parcel in) { 672 HotlistSettings settings = new HotlistSettings(); 673 settings.apLostThreshold = in.readInt(); 674 int n = in.readInt(); 675 settings.bssidInfos = new BssidInfo[n]; 676 for (int i = 0; i < n; i++) { 677 BssidInfo info = new BssidInfo(); 678 info.bssid = in.readString(); 679 info.low = in.readInt(); 680 info.high = in.readInt(); 681 info.frequencyHint = in.readInt(); 682 settings.bssidInfos[i] = info; 683 } 684 return settings; 685 } 686 687 public HotlistSettings[] newArray(int size) { 688 return new HotlistSettings[size]; 689 } 690 }; 691 } 692 693 /** 694 * set interesting access points to find 695 * @param bssidInfos access points of interest 696 * @param apLostThreshold number of scans needed to indicate that AP is lost 697 * @param listener object provided to report events on; this object must be unique and must 698 * also be provided on {@link #stopTrackingBssids} 699 */ 700 public void startTrackingBssids(BssidInfo[] bssidInfos, 701 int apLostThreshold, BssidListener listener) { 702 validateChannel(); 703 HotlistSettings settings = new HotlistSettings(); 704 settings.bssidInfos = bssidInfos; 705 sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings); 706 } 707 708 /** 709 * remove tracking of interesting access points 710 * @param listener same object provided in {@link #startTrackingBssids} 711 */ 712 public void stopTrackingBssids(BssidListener listener) { 713 validateChannel(); 714 sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); 715 } 716 717 718 /* private members and methods */ 719 720 private static final String TAG = "WifiScanner"; 721 private static final boolean DBG = true; 722 723 /* commands for Wifi Service */ 724 private static final int BASE = Protocol.BASE_WIFI_SCANNER; 725 726 /** @hide */ 727 public static final int CMD_SCAN = BASE + 0; 728 /** @hide */ 729 public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; 730 /** @hide */ 731 public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; 732 /** @hide */ 733 public static final int CMD_GET_SCAN_RESULTS = BASE + 4; 734 /** @hide */ 735 public static final int CMD_SCAN_RESULT = BASE + 5; 736 /** @hide */ 737 public static final int CMD_SET_HOTLIST = BASE + 6; 738 /** @hide */ 739 public static final int CMD_RESET_HOTLIST = BASE + 7; 740 /** @hide */ 741 public static final int CMD_AP_FOUND = BASE + 9; 742 /** @hide */ 743 public static final int CMD_AP_LOST = BASE + 10; 744 /** @hide */ 745 public static final int CMD_START_TRACKING_CHANGE = BASE + 11; 746 /** @hide */ 747 public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; 748 /** @hide */ 749 public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; 750 /** @hide */ 751 public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; 752 /** @hide */ 753 public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; 754 /** @hide */ 755 public static final int CMD_OP_SUCCEEDED = BASE + 17; 756 /** @hide */ 757 public static final int CMD_OP_FAILED = BASE + 18; 758 /** @hide */ 759 public static final int CMD_PERIOD_CHANGED = BASE + 19; 760 /** @hide */ 761 public static final int CMD_FULL_SCAN_RESULT = BASE + 20; 762 /** @hide */ 763 public static final int CMD_START_SINGLE_SCAN = BASE + 21; 764 /** @hide */ 765 public static final int CMD_STOP_SINGLE_SCAN = BASE + 22; 766 /** @hide */ 767 public static final int CMD_SINGLE_SCAN_COMPLETED = BASE + 23; 768 769 private Context mContext; 770 private IWifiScanner mService; 771 772 private static final int INVALID_KEY = 0; 773 private static int sListenerKey = 1; 774 775 private static final SparseArray sListenerMap = new SparseArray(); 776 private static final Object sListenerMapLock = new Object(); 777 778 private static AsyncChannel sAsyncChannel; 779 private static CountDownLatch sConnected; 780 781 private static final Object sThreadRefLock = new Object(); 782 private static int sThreadRefCount; 783 private static HandlerThread sHandlerThread; 784 785 /** 786 * Create a new WifiScanner instance. 787 * Applications will almost always want to use 788 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 789 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. 790 * @param context the application context 791 * @param service the Binder interface 792 * @hide 793 */ 794 public WifiScanner(Context context, IWifiScanner service) { 795 mContext = context; 796 mService = service; 797 init(); 798 } 799 800 private void init() { 801 synchronized (sThreadRefLock) { 802 if (++sThreadRefCount == 1) { 803 Messenger messenger = null; 804 try { 805 messenger = mService.getMessenger(); 806 } catch (RemoteException e) { 807 /* do nothing */ 808 } catch (SecurityException e) { 809 /* do nothing */ 810 } 811 812 if (messenger == null) { 813 sAsyncChannel = null; 814 return; 815 } 816 817 sHandlerThread = new HandlerThread("WifiScanner"); 818 sAsyncChannel = new AsyncChannel(); 819 sConnected = new CountDownLatch(1); 820 821 sHandlerThread.start(); 822 Handler handler = new ServiceHandler(sHandlerThread.getLooper()); 823 sAsyncChannel.connect(mContext, handler, messenger); 824 try { 825 sConnected.await(); 826 } catch (InterruptedException e) { 827 Log.e(TAG, "interrupted wait at init"); 828 } 829 } 830 } 831 } 832 833 private void validateChannel() { 834 if (sAsyncChannel == null) throw new IllegalStateException( 835 "No permission to access and change wifi or a bad initialization"); 836 } 837 838 private static int putListener(Object listener) { 839 if (listener == null) return INVALID_KEY; 840 int key; 841 synchronized (sListenerMapLock) { 842 do { 843 key = sListenerKey++; 844 } while (key == INVALID_KEY); 845 sListenerMap.put(key, listener); 846 } 847 return key; 848 } 849 850 private static Object getListener(int key) { 851 if (key == INVALID_KEY) return null; 852 synchronized (sListenerMapLock) { 853 Object listener = sListenerMap.get(key); 854 return listener; 855 } 856 } 857 858 private static int getListenerKey(Object listener) { 859 if (listener == null) return INVALID_KEY; 860 synchronized (sListenerMapLock) { 861 int index = sListenerMap.indexOfValue(listener); 862 if (index == -1) { 863 return INVALID_KEY; 864 } else { 865 return sListenerMap.keyAt(index); 866 } 867 } 868 } 869 870 private static Object removeListener(int key) { 871 if (key == INVALID_KEY) return null; 872 synchronized (sListenerMapLock) { 873 Object listener = sListenerMap.get(key); 874 sListenerMap.remove(key); 875 return listener; 876 } 877 } 878 879 private static int removeListener(Object listener) { 880 int key = getListenerKey(listener); 881 if (key == INVALID_KEY) return key; 882 synchronized (sListenerMapLock) { 883 sListenerMap.remove(key); 884 return key; 885 } 886 } 887 888 /** @hide */ 889 public static class OperationResult implements Parcelable { 890 public int reason; 891 public String description; 892 893 public OperationResult(int reason, String description) { 894 this.reason = reason; 895 this.description = description; 896 } 897 898 /** Implement the Parcelable interface {@hide} */ 899 public int describeContents() { 900 return 0; 901 } 902 903 /** Implement the Parcelable interface {@hide} */ 904 public void writeToParcel(Parcel dest, int flags) { 905 dest.writeInt(reason); 906 dest.writeString(description); 907 } 908 909 /** Implement the Parcelable interface {@hide} */ 910 public static final Creator<OperationResult> CREATOR = 911 new Creator<OperationResult>() { 912 public OperationResult createFromParcel(Parcel in) { 913 int reason = in.readInt(); 914 String description = in.readString(); 915 return new OperationResult(reason, description); 916 } 917 918 public OperationResult[] newArray(int size) { 919 return new OperationResult[size]; 920 } 921 }; 922 } 923 924 private static class ServiceHandler extends Handler { 925 ServiceHandler(Looper looper) { 926 super(looper); 927 } 928 @Override 929 public void handleMessage(Message msg) { 930 switch (msg.what) { 931 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 932 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 933 sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 934 } else { 935 Log.e(TAG, "Failed to set up channel connection"); 936 // This will cause all further async API calls on the WifiManager 937 // to fail and throw an exception 938 sAsyncChannel = null; 939 } 940 sConnected.countDown(); 941 return; 942 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 943 return; 944 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 945 Log.e(TAG, "Channel connection lost"); 946 // This will cause all further async API calls on the WifiManager 947 // to fail and throw an exception 948 sAsyncChannel = null; 949 getLooper().quit(); 950 return; 951 } 952 953 Object listener = getListener(msg.arg2); 954 955 if (listener == null) { 956 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2); 957 return; 958 } else { 959 if (DBG) Log.d(TAG, "listener key = " + msg.arg2); 960 } 961 962 switch (msg.what) { 963 /* ActionListeners grouped together */ 964 case CMD_OP_SUCCEEDED : 965 ((ActionListener) listener).onSuccess(); 966 break; 967 case CMD_OP_FAILED : { 968 OperationResult result = (OperationResult)msg.obj; 969 ((ActionListener) listener).onFailure(result.reason, result.description); 970 removeListener(msg.arg2); 971 } 972 break; 973 case CMD_SCAN_RESULT : 974 ((ScanListener) listener).onResults( 975 ((ParcelableScanData) msg.obj).getResults()); 976 return; 977 case CMD_FULL_SCAN_RESULT : 978 ScanResult result = (ScanResult) msg.obj; 979 ((ScanListener) listener).onFullResult(result); 980 return; 981 case CMD_PERIOD_CHANGED: 982 ((ScanListener) listener).onPeriodChanged(msg.arg1); 983 return; 984 case CMD_AP_FOUND: 985 ((BssidListener) listener).onFound( 986 ((ParcelableScanResults) msg.obj).getResults()); 987 return; 988 case CMD_WIFI_CHANGE_DETECTED: 989 ((WifiChangeListener) listener).onChanging( 990 ((ParcelableScanResults) msg.obj).getResults()); 991 return; 992 case CMD_WIFI_CHANGES_STABILIZED: 993 ((WifiChangeListener) listener).onQuiescence( 994 ((ParcelableScanResults) msg.obj).getResults()); 995 return; 996 case CMD_SINGLE_SCAN_COMPLETED: 997 Log.d(TAG, "removing listener for single scan"); 998 removeListener(msg.arg2); 999 break; 1000 default: 1001 if (DBG) Log.d(TAG, "Ignoring message " + msg.what); 1002 return; 1003 } 1004 } 1005 } 1006} 1007