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