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.Looper; 24 import android.os.Message; 25 import android.os.Messenger; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.os.RemoteException; 29 import android.os.WorkSource; 30 import android.util.Log; 31 import android.util.SparseArray; 32 33 import com.android.internal.util.AsyncChannel; 34 import com.android.internal.util.Preconditions; 35 import com.android.internal.util.Protocol; 36 37 import java.util.List; 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 /** An outstanding request with the same listener hasn't finished yet. */ 82 public static final int REASON_DUPLICATE_REQEUST = -5; 83 84 /** @hide */ 85 public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels"; 86 87 /** 88 * Generic action callback invocation interface 89 * @hide 90 */ 91 @SystemApi 92 public static interface ActionListener { 93 public void onSuccess(); 94 public void onFailure(int reason, String description); 95 } 96 97 /** 98 * gives you all the possible channels; channel is specified as an 99 * integer with frequency in MHz i.e. channel 1 is 2412 100 * @hide 101 */ 102 public List<Integer> getAvailableChannels(int band) { 103 try { 104 Bundle bundle = mService.getAvailableChannels(band); 105 return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA); 106 } catch (RemoteException e) { 107 return null; 108 } 109 } 110 111 /** 112 * provides channel specification for scanning 113 */ 114 public static class ChannelSpec { 115 /** 116 * channel frequency in MHz; for example channel 1 is specified as 2412 117 */ 118 public int frequency; 119 /** 120 * if true, scan this channel in passive fashion. 121 * This flag is ignored on DFS channel specification. 122 * @hide 123 */ 124 public boolean passive; /* ignored on DFS channels */ 125 /** 126 * how long to dwell on this channel 127 * @hide 128 */ 129 public int dwellTimeMS; /* not supported for now */ 130 131 /** 132 * default constructor for channel spec 133 */ 134 public ChannelSpec(int frequency) { 135 this.frequency = frequency; 136 passive = false; 137 dwellTimeMS = 0; 138 } 139 } 140 141 /** 142 * reports {@link ScanListener#onResults} when underlying buffers are full 143 * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag 144 * @deprecated It is not supported anymore. 145 */ 146 @Deprecated 147 public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; 148 /** 149 * reports {@link ScanListener#onResults} after each scan 150 */ 151 public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0); 152 /** 153 * reports {@link ScanListener#onFullResult} whenever each beacon is discovered 154 */ 155 public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1); 156 /** 157 * Do not place scans in the chip's scan history buffer 158 */ 159 public static final int REPORT_EVENT_NO_BATCH = (1 << 2); 160 161 162 /** {@hide} */ 163 public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; 164 /** {@hide} */ 165 public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource"; 166 /** 167 * scan configuration parameters to be sent to {@link #startBackgroundScan} 168 */ 169 public static class ScanSettings implements Parcelable { 170 171 /** one of the WIFI_BAND values */ 172 public int band; 173 /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ 174 public ChannelSpec[] channels; 175 /** 176 * list of networkId's of hidden networks to scan for. 177 * These Id's should correspond to the wpa_supplicant's networkId's and will be used 178 * in connectivity scans using wpa_supplicant. 179 * {@hide} 180 * */ 181 public int[] hiddenNetworkIds; 182 /** period of background scan; in millisecond, 0 => single shot scan */ 183 public int periodInMs; 184 /** must have a valid REPORT_EVENT value */ 185 public int reportEvents; 186 /** defines number of bssids to cache from each scan */ 187 public int numBssidsPerScan; 188 /** 189 * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL 190 * to wake up at fixed interval 191 */ 192 public int maxScansToCache; 193 /** 194 * if maxPeriodInMs is non zero or different than period, then this bucket is 195 * a truncated binary exponential backoff bucket and the scan period will grow 196 * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount)) 197 * to maxPeriodInMs 198 */ 199 public int maxPeriodInMs; 200 /** 201 * for truncated binary exponential back off bucket, number of scans to perform 202 * for a given period 203 */ 204 public int stepCount; 205 /** 206 * Flag to indicate if the scan settings are targeted for PNO scan. 207 * {@hide} 208 */ 209 public boolean isPnoScan; 210 211 /** Implement the Parcelable interface {@hide} */ 212 public int describeContents() { 213 return 0; 214 } 215 216 /** Implement the Parcelable interface {@hide} */ 217 public void writeToParcel(Parcel dest, int flags) { 218 dest.writeInt(band); 219 dest.writeInt(periodInMs); 220 dest.writeInt(reportEvents); 221 dest.writeInt(numBssidsPerScan); 222 dest.writeInt(maxScansToCache); 223 dest.writeInt(maxPeriodInMs); 224 dest.writeInt(stepCount); 225 dest.writeInt(isPnoScan ? 1 : 0); 226 if (channels != null) { 227 dest.writeInt(channels.length); 228 for (int i = 0; i < channels.length; i++) { 229 dest.writeInt(channels[i].frequency); 230 dest.writeInt(channels[i].dwellTimeMS); 231 dest.writeInt(channels[i].passive ? 1 : 0); 232 } 233 } else { 234 dest.writeInt(0); 235 } 236 dest.writeIntArray(hiddenNetworkIds); 237 } 238 239 /** Implement the Parcelable interface {@hide} */ 240 public static final Creator<ScanSettings> CREATOR = 241 new Creator<ScanSettings>() { 242 public ScanSettings createFromParcel(Parcel in) { 243 ScanSettings settings = new ScanSettings(); 244 settings.band = in.readInt(); 245 settings.periodInMs = in.readInt(); 246 settings.reportEvents = in.readInt(); 247 settings.numBssidsPerScan = in.readInt(); 248 settings.maxScansToCache = in.readInt(); 249 settings.maxPeriodInMs = in.readInt(); 250 settings.stepCount = in.readInt(); 251 settings.isPnoScan = in.readInt() == 1; 252 int num_channels = in.readInt(); 253 settings.channels = new ChannelSpec[num_channels]; 254 for (int i = 0; i < num_channels; i++) { 255 int frequency = in.readInt(); 256 ChannelSpec spec = new ChannelSpec(frequency); 257 spec.dwellTimeMS = in.readInt(); 258 spec.passive = in.readInt() == 1; 259 settings.channels[i] = spec; 260 } 261 settings.hiddenNetworkIds = in.createIntArray(); 262 return settings; 263 } 264 265 public ScanSettings[] newArray(int size) { 266 return new ScanSettings[size]; 267 } 268 }; 269 270 } 271 272 /** 273 * all the information garnered from a single scan 274 */ 275 public static class ScanData implements Parcelable { 276 /** scan identifier */ 277 private int mId; 278 /** additional information about scan 279 * 0 => no special issues encountered in the scan 280 * non-zero => scan was truncated, so results may not be complete 281 */ 282 private int mFlags; 283 /** 284 * Indicates the buckets that were scanned to generate these results. 285 * This is not relevant to WifiScanner API users and is used internally. 286 * {@hide} 287 */ 288 private int mBucketsScanned; 289 /** 290 * Indicates that the scan results received are as a result of a scan of all available 291 * channels. This should only be expected to function for single scans. 292 * {@hide} 293 */ 294 private boolean mAllChannelsScanned; 295 /** all scan results discovered in this scan, sorted by timestamp in ascending order */ 296 private ScanResult mResults[]; 297 298 ScanData() {} 299 300 public ScanData(int id, int flags, ScanResult[] results) { 301 mId = id; 302 mFlags = flags; 303 mResults = results; 304 } 305 306 /** {@hide} */ 307 public ScanData(int id, int flags, int bucketsScanned, boolean allChannelsScanned, 308 ScanResult[] results) { 309 mId = id; 310 mFlags = flags; 311 mBucketsScanned = bucketsScanned; 312 mAllChannelsScanned = allChannelsScanned; 313 mResults = results; 314 } 315 316 public ScanData(ScanData s) { 317 mId = s.mId; 318 mFlags = s.mFlags; 319 mBucketsScanned = s.mBucketsScanned; 320 mAllChannelsScanned = s.mAllChannelsScanned; 321 mResults = new ScanResult[s.mResults.length]; 322 for (int i = 0; i < s.mResults.length; i++) { 323 ScanResult result = s.mResults[i]; 324 ScanResult newResult = new ScanResult(result); 325 mResults[i] = newResult; 326 } 327 } 328 329 public int getId() { 330 return mId; 331 } 332 333 public int getFlags() { 334 return mFlags; 335 } 336 337 /** {@hide} */ 338 public int getBucketsScanned() { 339 return mBucketsScanned; 340 } 341 342 /** {@hide} */ 343 public boolean isAllChannelsScanned() { 344 return mAllChannelsScanned; 345 } 346 347 public ScanResult[] getResults() { 348 return mResults; 349 } 350 351 /** Implement the Parcelable interface {@hide} */ 352 public int describeContents() { 353 return 0; 354 } 355 356 /** Implement the Parcelable interface {@hide} */ 357 public void writeToParcel(Parcel dest, int flags) { 358 if (mResults != null) { 359 dest.writeInt(mId); 360 dest.writeInt(mFlags); 361 dest.writeInt(mBucketsScanned); 362 dest.writeInt(mAllChannelsScanned ? 1 : 0); 363 dest.writeInt(mResults.length); 364 for (int i = 0; i < mResults.length; i++) { 365 ScanResult result = mResults[i]; 366 result.writeToParcel(dest, flags); 367 } 368 } else { 369 dest.writeInt(0); 370 } 371 } 372 373 /** Implement the Parcelable interface {@hide} */ 374 public static final Creator<ScanData> CREATOR = 375 new Creator<ScanData>() { 376 public ScanData createFromParcel(Parcel in) { 377 int id = in.readInt(); 378 int flags = in.readInt(); 379 int bucketsScanned = in.readInt(); 380 boolean allChannelsScanned = in.readInt() != 0; 381 int n = in.readInt(); 382 ScanResult results[] = new ScanResult[n]; 383 for (int i = 0; i < n; i++) { 384 results[i] = ScanResult.CREATOR.createFromParcel(in); 385 } 386 return new ScanData(id, flags, bucketsScanned, allChannelsScanned, results); 387 } 388 389 public ScanData[] newArray(int size) { 390 return new ScanData[size]; 391 } 392 }; 393 } 394 395 public static class ParcelableScanData implements Parcelable { 396 397 public ScanData mResults[]; 398 399 public ParcelableScanData(ScanData[] results) { 400 mResults = results; 401 } 402 403 public ScanData[] getResults() { 404 return mResults; 405 } 406 407 /** Implement the Parcelable interface {@hide} */ 408 public int describeContents() { 409 return 0; 410 } 411 412 /** Implement the Parcelable interface {@hide} */ 413 public void writeToParcel(Parcel dest, int flags) { 414 if (mResults != null) { 415 dest.writeInt(mResults.length); 416 for (int i = 0; i < mResults.length; i++) { 417 ScanData result = mResults[i]; 418 result.writeToParcel(dest, flags); 419 } 420 } else { 421 dest.writeInt(0); 422 } 423 } 424 425 /** Implement the Parcelable interface {@hide} */ 426 public static final Creator<ParcelableScanData> CREATOR = 427 new Creator<ParcelableScanData>() { 428 public ParcelableScanData createFromParcel(Parcel in) { 429 int n = in.readInt(); 430 ScanData results[] = new ScanData[n]; 431 for (int i = 0; i < n; i++) { 432 results[i] = ScanData.CREATOR.createFromParcel(in); 433 } 434 return new ParcelableScanData(results); 435 } 436 437 public ParcelableScanData[] newArray(int size) { 438 return new ParcelableScanData[size]; 439 } 440 }; 441 } 442 443 public static class ParcelableScanResults implements Parcelable { 444 445 public ScanResult mResults[]; 446 447 public ParcelableScanResults(ScanResult[] results) { 448 mResults = results; 449 } 450 451 public ScanResult[] getResults() { 452 return mResults; 453 } 454 455 /** Implement the Parcelable interface {@hide} */ 456 public int describeContents() { 457 return 0; 458 } 459 460 /** Implement the Parcelable interface {@hide} */ 461 public void writeToParcel(Parcel dest, int flags) { 462 if (mResults != null) { 463 dest.writeInt(mResults.length); 464 for (int i = 0; i < mResults.length; i++) { 465 ScanResult result = mResults[i]; 466 result.writeToParcel(dest, flags); 467 } 468 } else { 469 dest.writeInt(0); 470 } 471 } 472 473 /** Implement the Parcelable interface {@hide} */ 474 public static final Creator<ParcelableScanResults> CREATOR = 475 new Creator<ParcelableScanResults>() { 476 public ParcelableScanResults createFromParcel(Parcel in) { 477 int n = in.readInt(); 478 ScanResult results[] = new ScanResult[n]; 479 for (int i = 0; i < n; i++) { 480 results[i] = ScanResult.CREATOR.createFromParcel(in); 481 } 482 return new ParcelableScanResults(results); 483 } 484 485 public ParcelableScanResults[] newArray(int size) { 486 return new ParcelableScanResults[size]; 487 } 488 }; 489 } 490 491 /** {@hide} */ 492 public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings"; 493 /** {@hide} */ 494 public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; 495 /** 496 * PNO scan configuration parameters to be sent to {@link #startPnoScan}. 497 * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API. 498 * {@hide} 499 */ 500 public static class PnoSettings implements Parcelable { 501 /** 502 * Pno network to be added to the PNO scan filtering. 503 * {@hide} 504 */ 505 public static class PnoNetwork { 506 /* 507 * Pno flags bitmask to be set in {@link #PnoNetwork.flags} 508 */ 509 /** Whether directed scan needs to be performed (for hidden SSIDs) */ 510 public static final byte FLAG_DIRECTED_SCAN = (1 << 0); 511 /** Whether PNO event shall be triggered if the network is found on A band */ 512 public static final byte FLAG_A_BAND = (1 << 1); 513 /** Whether PNO event shall be triggered if the network is found on G band */ 514 public static final byte FLAG_G_BAND = (1 << 2); 515 /** 516 * Whether strict matching is required 517 * If required then the firmware must store the network's SSID and not just a hash 518 */ 519 public static final byte FLAG_STRICT_MATCH = (1 << 3); 520 /** 521 * If this SSID should be considered the same network as the currently connected 522 * one for scoring. 523 */ 524 public static final byte FLAG_SAME_NETWORK = (1 << 4); 525 526 /* 527 * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in 528 * {@link #PnoNetwork.authBitField} 529 */ 530 /** Open Network */ 531 public static final byte AUTH_CODE_OPEN = (1 << 0); 532 /** WPA_PSK or WPA2PSK */ 533 public static final byte AUTH_CODE_PSK = (1 << 1); 534 /** any EAPOL */ 535 public static final byte AUTH_CODE_EAPOL = (1 << 2); 536 537 /** SSID of the network */ 538 public String ssid; 539 /** Network ID in wpa_supplicant */ 540 public int networkId; 541 /** Assigned priority for the network */ 542 public int priority; 543 /** Bitmask of the FLAG_XXX */ 544 public byte flags; 545 /** Bitmask of the ATUH_XXX */ 546 public byte authBitField; 547 548 /** 549 * default constructor for PnoNetwork 550 */ 551 public PnoNetwork(String ssid) { 552 this.ssid = ssid; 553 flags = 0; 554 authBitField = 0; 555 } 556 } 557 558 /** Connected vs Disconnected PNO flag {@hide} */ 559 public boolean isConnected; 560 /** Minimum 5GHz RSSI for a BSSID to be considered */ 561 public int min5GHzRssi; 562 /** Minimum 2.4GHz RSSI for a BSSID to be considered */ 563 public int min24GHzRssi; 564 /** Maximum score that a network can have before bonuses */ 565 public int initialScoreMax; 566 /** 567 * Only report when there is a network's score this much higher 568 * than the current connection. 569 */ 570 public int currentConnectionBonus; 571 /** score bonus for all networks with the same network flag */ 572 public int sameNetworkBonus; 573 /** score bonus for networks that are not open */ 574 public int secureBonus; 575 /** 5GHz RSSI score bonus (applied to all 5GHz networks) */ 576 public int band5GHzBonus; 577 /** Pno Network filter list */ 578 public PnoNetwork[] networkList; 579 580 /** Implement the Parcelable interface {@hide} */ 581 public int describeContents() { 582 return 0; 583 } 584 585 /** Implement the Parcelable interface {@hide} */ 586 public void writeToParcel(Parcel dest, int flags) { 587 dest.writeInt(isConnected ? 1 : 0); 588 dest.writeInt(min5GHzRssi); 589 dest.writeInt(min24GHzRssi); 590 dest.writeInt(initialScoreMax); 591 dest.writeInt(currentConnectionBonus); 592 dest.writeInt(sameNetworkBonus); 593 dest.writeInt(secureBonus); 594 dest.writeInt(band5GHzBonus); 595 if (networkList != null) { 596 dest.writeInt(networkList.length); 597 for (int i = 0; i < networkList.length; i++) { 598 dest.writeString(networkList[i].ssid); 599 dest.writeInt(networkList[i].networkId); 600 dest.writeInt(networkList[i].priority); 601 dest.writeByte(networkList[i].flags); 602 dest.writeByte(networkList[i].authBitField); 603 } 604 } else { 605 dest.writeInt(0); 606 } 607 } 608 609 /** Implement the Parcelable interface {@hide} */ 610 public static final Creator<PnoSettings> CREATOR = 611 new Creator<PnoSettings>() { 612 public PnoSettings createFromParcel(Parcel in) { 613 PnoSettings settings = new PnoSettings(); 614 settings.isConnected = in.readInt() == 1; 615 settings.min5GHzRssi = in.readInt(); 616 settings.min24GHzRssi = in.readInt(); 617 settings.initialScoreMax = in.readInt(); 618 settings.currentConnectionBonus = in.readInt(); 619 settings.sameNetworkBonus = in.readInt(); 620 settings.secureBonus = in.readInt(); 621 settings.band5GHzBonus = in.readInt(); 622 int numNetworks = in.readInt(); 623 settings.networkList = new PnoNetwork[numNetworks]; 624 for (int i = 0; i < numNetworks; i++) { 625 String ssid = in.readString(); 626 PnoNetwork network = new PnoNetwork(ssid); 627 network.networkId = in.readInt(); 628 network.priority = in.readInt(); 629 network.flags = in.readByte(); 630 network.authBitField = in.readByte(); 631 settings.networkList[i] = network; 632 } 633 return settings; 634 } 635 636 public PnoSettings[] newArray(int size) { 637 return new PnoSettings[size]; 638 } 639 }; 640 641 } 642 643 /** 644 * interface to get scan events on; specify this on {@link #startBackgroundScan} or 645 * {@link #startScan} 646 */ 647 public interface ScanListener extends ActionListener { 648 /** 649 * Framework co-ordinates scans across multiple apps; so it may not give exactly the 650 * same period requested. If period of a scan is changed; it is reported by this event. 651 */ 652 public void onPeriodChanged(int periodInMs); 653 /** 654 * reports results retrieved from background scan and single shot scans 655 */ 656 public void onResults(ScanData[] results); 657 /** 658 * reports full scan result for each access point found in scan 659 */ 660 public void onFullResult(ScanResult fullScanResult); 661 } 662 663 /** 664 * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and 665 * {@link #startConnectedPnoScan}. 666 * {@hide} 667 */ 668 public interface PnoScanListener extends ScanListener { 669 /** 670 * Invoked when one of the PNO networks are found in scan results. 671 */ 672 void onPnoNetworkFound(ScanResult[] results); 673 } 674 675 /** 676 * Register a listener that will receive results from all single scans 677 * Either the onSuccess/onFailure will be called once when the listener is registered. After 678 * (assuming onSuccess was called) all subsequent single scan results will be delivered to the 679 * listener. It is possible that onFullResult will not be called for all results of the first 680 * scan if the listener was registered during the scan. 681 * 682 * @param listener specifies the object to report events to. This object is also treated as a 683 * key for this request, and must also be specified to cancel the request. 684 * Multiple requests should also not share this object. 685 * {@hide} 686 */ 687 public void registerScanListener(ScanListener listener) { 688 Preconditions.checkNotNull(listener, "listener cannot be null"); 689 int key = addListener(listener); 690 if (key == INVALID_KEY) return; 691 validateChannel(); 692 mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key); 693 } 694 695 /** 696 * Deregister a listener for ongoing single scans 697 * @param listener specifies which scan to cancel; must be same object as passed in {@link 698 * #registerScanListener} 699 * {@hide} 700 */ 701 public void deregisterScanListener(ScanListener listener) { 702 Preconditions.checkNotNull(listener, "listener cannot be null"); 703 int key = removeListener(listener); 704 if (key == INVALID_KEY) return; 705 validateChannel(); 706 mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key); 707 } 708 709 /** start wifi scan in background 710 * @param settings specifies various parameters for the scan; for more information look at 711 * {@link ScanSettings} 712 * @param listener specifies the object to report events to. This object is also treated as a 713 * key for this scan, and must also be specified to cancel the scan. Multiple 714 * scans should also not share this object. 715 */ 716 public void startBackgroundScan(ScanSettings settings, ScanListener listener) { 717 startBackgroundScan(settings, listener, null); 718 } 719 720 /** start wifi scan in background 721 * @param settings specifies various parameters for the scan; for more information look at 722 * {@link ScanSettings} 723 * @param workSource WorkSource to blame for power usage 724 * @param listener specifies the object to report events to. This object is also treated as a 725 * key for this scan, and must also be specified to cancel the scan. Multiple 726 * scans should also not share this object. 727 */ 728 public void startBackgroundScan(ScanSettings settings, ScanListener listener, 729 WorkSource workSource) { 730 Preconditions.checkNotNull(listener, "listener cannot be null"); 731 int key = addListener(listener); 732 if (key == INVALID_KEY) return; 733 validateChannel(); 734 Bundle scanParams = new Bundle(); 735 scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); 736 scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource); 737 mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams); 738 } 739 740 /** 741 * stop an ongoing wifi scan 742 * @param listener specifies which scan to cancel; must be same object as passed in {@link 743 * #startBackgroundScan} 744 */ 745 public void stopBackgroundScan(ScanListener listener) { 746 Preconditions.checkNotNull(listener, "listener cannot be null"); 747 int key = removeListener(listener); 748 if (key == INVALID_KEY) return; 749 validateChannel(); 750 mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key); 751 } 752 /** 753 * reports currently available scan results on appropriate listeners 754 * @return true if all scan results were reported correctly 755 */ 756 public boolean getScanResults() { 757 validateChannel(); 758 Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0); 759 return reply.what == CMD_OP_SUCCEEDED; 760 } 761 762 /** 763 * starts a single scan and reports results asynchronously 764 * @param settings specifies various parameters for the scan; for more information look at 765 * {@link ScanSettings} 766 * @param listener specifies the object to report events to. This object is also treated as a 767 * key for this scan, and must also be specified to cancel the scan. Multiple 768 * scans should also not share this object. 769 */ 770 public void startScan(ScanSettings settings, ScanListener listener) { 771 startScan(settings, listener, null); 772 } 773 774 /** 775 * starts a single scan and reports results asynchronously 776 * @param settings specifies various parameters for the scan; for more information look at 777 * {@link ScanSettings} 778 * @param workSource WorkSource to blame for power usage 779 * @param listener specifies the object to report events to. This object is also treated as a 780 * key for this scan, and must also be specified to cancel the scan. Multiple 781 * scans should also not share this object. 782 */ 783 public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { 784 Preconditions.checkNotNull(listener, "listener cannot be null"); 785 int key = addListener(listener); 786 if (key == INVALID_KEY) return; 787 validateChannel(); 788 Bundle scanParams = new Bundle(); 789 scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); 790 scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource); 791 mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams); 792 } 793 794 /** 795 * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults() 796 * hasn't been called on the listener, ignored otherwise 797 * @param listener 798 */ 799 public void stopScan(ScanListener listener) { 800 Preconditions.checkNotNull(listener, "listener cannot be null"); 801 int key = removeListener(listener); 802 if (key == INVALID_KEY) return; 803 validateChannel(); 804 mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key); 805 } 806 807 private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) { 808 // Bundle up both the settings and send it across. 809 Bundle pnoParams = new Bundle(); 810 // Set the PNO scan flag. 811 scanSettings.isPnoScan = true; 812 pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings); 813 pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings); 814 mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams); 815 } 816 /** 817 * Start wifi connected PNO scan 818 * @param scanSettings specifies various parameters for the scan; for more information look at 819 * {@link ScanSettings} 820 * @param pnoSettings specifies various parameters for PNO; for more information look at 821 * {@link PnoSettings} 822 * @param listener specifies the object to report events to. This object is also treated as a 823 * key for this scan, and must also be specified to cancel the scan. Multiple 824 * scans should also not share this object. 825 * {@hide} 826 */ 827 public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, 828 PnoScanListener listener) { 829 Preconditions.checkNotNull(listener, "listener cannot be null"); 830 Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null"); 831 int key = addListener(listener); 832 if (key == INVALID_KEY) return; 833 validateChannel(); 834 pnoSettings.isConnected = true; 835 startPnoScan(scanSettings, pnoSettings, key); 836 } 837 /** 838 * Start wifi disconnected PNO scan 839 * @param scanSettings specifies various parameters for the scan; for more information look at 840 * {@link ScanSettings} 841 * @param pnoSettings specifies various parameters for PNO; for more information look at 842 * {@link PnoSettings} 843 * @param listener specifies the object to report events to. This object is also treated as a 844 * key for this scan, and must also be specified to cancel the scan. Multiple 845 * scans should also not share this object. 846 * {@hide} 847 */ 848 public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, 849 PnoScanListener listener) { 850 Preconditions.checkNotNull(listener, "listener cannot be null"); 851 Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null"); 852 int key = addListener(listener); 853 if (key == INVALID_KEY) return; 854 validateChannel(); 855 pnoSettings.isConnected = false; 856 startPnoScan(scanSettings, pnoSettings, key); 857 } 858 /** 859 * Stop an ongoing wifi PNO scan 860 * @param listener specifies which scan to cancel; must be same object as passed in {@link 861 * #startPnoScan} 862 * TODO(rpius): Check if we can remove pnoSettings param in stop. 863 * {@hide} 864 */ 865 public void stopPnoScan(ScanListener listener) { 866 Preconditions.checkNotNull(listener, "listener cannot be null"); 867 int key = removeListener(listener); 868 if (key == INVALID_KEY) return; 869 validateChannel(); 870 mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key); 871 } 872 873 /** specifies information about an access point of interest */ 874 public static class BssidInfo { 875 /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */ 876 public String bssid; 877 /** low signal strength threshold; more information at {@link ScanResult#level} */ 878 public int low; /* minimum RSSI */ 879 /** high signal threshold; more information at {@link ScanResult#level} */ 880 public int high; /* maximum RSSI */ 881 /** channel frequency (in KHz) where you may find this BSSID */ 882 public int frequencyHint; 883 } 884 885 /** @hide */ 886 @SystemApi 887 public static class WifiChangeSettings implements Parcelable { 888 public int rssiSampleSize; /* sample size for RSSI averaging */ 889 public int lostApSampleSize; /* samples to confirm AP's loss */ 890 public int unchangedSampleSize; /* samples to confirm no change */ 891 public int minApsBreachingThreshold; /* change threshold to trigger event */ 892 public int periodInMs; /* scan period in millisecond */ 893 public BssidInfo[] bssidInfos; 894 895 /** Implement the Parcelable interface {@hide} */ 896 public int describeContents() { 897 return 0; 898 } 899 900 /** Implement the Parcelable interface {@hide} */ 901 public void writeToParcel(Parcel dest, int flags) { 902 dest.writeInt(rssiSampleSize); 903 dest.writeInt(lostApSampleSize); 904 dest.writeInt(unchangedSampleSize); 905 dest.writeInt(minApsBreachingThreshold); 906 dest.writeInt(periodInMs); 907 if (bssidInfos != null) { 908 dest.writeInt(bssidInfos.length); 909 for (int i = 0; i < bssidInfos.length; i++) { 910 BssidInfo info = bssidInfos[i]; 911 dest.writeString(info.bssid); 912 dest.writeInt(info.low); 913 dest.writeInt(info.high); 914 dest.writeInt(info.frequencyHint); 915 } 916 } else { 917 dest.writeInt(0); 918 } 919 } 920 921 /** Implement the Parcelable interface {@hide} */ 922 public static final Creator<WifiChangeSettings> CREATOR = 923 new Creator<WifiChangeSettings>() { 924 public WifiChangeSettings createFromParcel(Parcel in) { 925 WifiChangeSettings settings = new WifiChangeSettings(); 926 settings.rssiSampleSize = in.readInt(); 927 settings.lostApSampleSize = in.readInt(); 928 settings.unchangedSampleSize = in.readInt(); 929 settings.minApsBreachingThreshold = in.readInt(); 930 settings.periodInMs = in.readInt(); 931 int len = in.readInt(); 932 settings.bssidInfos = new BssidInfo[len]; 933 for (int i = 0; i < len; i++) { 934 BssidInfo info = new BssidInfo(); 935 info.bssid = in.readString(); 936 info.low = in.readInt(); 937 info.high = in.readInt(); 938 info.frequencyHint = in.readInt(); 939 settings.bssidInfos[i] = info; 940 } 941 return settings; 942 } 943 944 public WifiChangeSettings[] newArray(int size) { 945 return new WifiChangeSettings[size]; 946 } 947 }; 948 949 } 950 951 /** configure WifiChange detection 952 * @param rssiSampleSize number of samples used for RSSI averaging 953 * @param lostApSampleSize number of samples to confirm an access point's loss 954 * @param unchangedSampleSize number of samples to confirm there are no changes 955 * @param minApsBreachingThreshold minimum number of access points that need to be 956 * out of range to detect WifiChange 957 * @param periodInMs indicates period of scan to find changes 958 * @param bssidInfos access points to watch 959 */ 960 public void configureWifiChange( 961 int rssiSampleSize, /* sample size for RSSI averaging */ 962 int lostApSampleSize, /* samples to confirm AP's loss */ 963 int unchangedSampleSize, /* samples to confirm no change */ 964 int minApsBreachingThreshold, /* change threshold to trigger event */ 965 int periodInMs, /* period of scan */ 966 BssidInfo[] bssidInfos /* signal thresholds to crosss */ 967 ) 968 { 969 validateChannel(); 970 971 WifiChangeSettings settings = new WifiChangeSettings(); 972 settings.rssiSampleSize = rssiSampleSize; 973 settings.lostApSampleSize = lostApSampleSize; 974 settings.unchangedSampleSize = unchangedSampleSize; 975 settings.minApsBreachingThreshold = minApsBreachingThreshold; 976 settings.periodInMs = periodInMs; 977 settings.bssidInfos = bssidInfos; 978 979 configureWifiChange(settings); 980 } 981 982 /** 983 * interface to get wifi change events on; use this on {@link #startTrackingWifiChange} 984 */ 985 public interface WifiChangeListener extends ActionListener { 986 /** indicates that changes were detected in wifi environment 987 * @param results indicate the access points that exhibited change 988 */ 989 public void onChanging(ScanResult[] results); /* changes are found */ 990 /** indicates that no wifi changes are being detected for a while 991 * @param results indicate the access points that are bing monitored for change 992 */ 993 public void onQuiescence(ScanResult[] results); /* changes settled down */ 994 } 995 996 /** 997 * track changes in wifi environment 998 * @param listener object to report events on; this object must be unique and must also be 999 * provided on {@link #stopTrackingWifiChange} 1000 */ 1001 public void startTrackingWifiChange(WifiChangeListener listener) { 1002 Preconditions.checkNotNull(listener, "listener cannot be null"); 1003 int key = addListener(listener); 1004 if (key == INVALID_KEY) return; 1005 validateChannel(); 1006 mAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key); 1007 } 1008 1009 /** 1010 * stop tracking changes in wifi environment 1011 * @param listener object that was provided to report events on {@link 1012 * #stopTrackingWifiChange} 1013 */ 1014 public void stopTrackingWifiChange(WifiChangeListener listener) { 1015 int key = removeListener(listener); 1016 if (key == INVALID_KEY) return; 1017 validateChannel(); 1018 mAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key); 1019 } 1020 1021 /** @hide */ 1022 @SystemApi 1023 public void configureWifiChange(WifiChangeSettings settings) { 1024 validateChannel(); 1025 mAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); 1026 } 1027 1028 /** interface to receive hotlist events on; use this on {@link #setHotlist} */ 1029 public static interface BssidListener extends ActionListener { 1030 /** indicates that access points were found by on going scans 1031 * @param results list of scan results, one for each access point visible currently 1032 */ 1033 public void onFound(ScanResult[] results); 1034 /** indicates that access points were missed by on going scans 1035 * @param results list of scan results, for each access point that is not visible anymore 1036 */ 1037 public void onLost(ScanResult[] results); 1038 } 1039 1040 /** @hide */ 1041 @SystemApi 1042 public static class HotlistSettings implements Parcelable { 1043 public BssidInfo[] bssidInfos; 1044 public int apLostThreshold; 1045 1046 /** Implement the Parcelable interface {@hide} */ 1047 public int describeContents() { 1048 return 0; 1049 } 1050 1051 /** Implement the Parcelable interface {@hide} */ 1052 public void writeToParcel(Parcel dest, int flags) { 1053 dest.writeInt(apLostThreshold); 1054 1055 if (bssidInfos != null) { 1056 dest.writeInt(bssidInfos.length); 1057 for (int i = 0; i < bssidInfos.length; i++) { 1058 BssidInfo info = bssidInfos[i]; 1059 dest.writeString(info.bssid); 1060 dest.writeInt(info.low); 1061 dest.writeInt(info.high); 1062 dest.writeInt(info.frequencyHint); 1063 } 1064 } else { 1065 dest.writeInt(0); 1066 } 1067 } 1068 1069 /** Implement the Parcelable interface {@hide} */ 1070 public static final Creator<HotlistSettings> CREATOR = 1071 new Creator<HotlistSettings>() { 1072 public HotlistSettings createFromParcel(Parcel in) { 1073 HotlistSettings settings = new HotlistSettings(); 1074 settings.apLostThreshold = in.readInt(); 1075 int n = in.readInt(); 1076 settings.bssidInfos = new BssidInfo[n]; 1077 for (int i = 0; i < n; i++) { 1078 BssidInfo info = new BssidInfo(); 1079 info.bssid = in.readString(); 1080 info.low = in.readInt(); 1081 info.high = in.readInt(); 1082 info.frequencyHint = in.readInt(); 1083 settings.bssidInfos[i] = info; 1084 } 1085 return settings; 1086 } 1087 1088 public HotlistSettings[] newArray(int size) { 1089 return new HotlistSettings[size]; 1090 } 1091 }; 1092 } 1093 1094 /** 1095 * set interesting access points to find 1096 * @param bssidInfos access points of interest 1097 * @param apLostThreshold number of scans needed to indicate that AP is lost 1098 * @param listener object provided to report events on; this object must be unique and must 1099 * also be provided on {@link #stopTrackingBssids} 1100 */ 1101 public void startTrackingBssids(BssidInfo[] bssidInfos, 1102 int apLostThreshold, BssidListener listener) { 1103 Preconditions.checkNotNull(listener, "listener cannot be null"); 1104 int key = addListener(listener); 1105 if (key == INVALID_KEY) return; 1106 validateChannel(); 1107 HotlistSettings settings = new HotlistSettings(); 1108 settings.bssidInfos = bssidInfos; 1109 settings.apLostThreshold = apLostThreshold; 1110 mAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings); 1111 } 1112 1113 /** 1114 * remove tracking of interesting access points 1115 * @param listener same object provided in {@link #startTrackingBssids} 1116 */ 1117 public void stopTrackingBssids(BssidListener listener) { 1118 Preconditions.checkNotNull(listener, "listener cannot be null"); 1119 int key = removeListener(listener); 1120 if (key == INVALID_KEY) return; 1121 validateChannel(); 1122 mAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key); 1123 } 1124 1125 1126 /* private members and methods */ 1127 1128 private static final String TAG = "WifiScanner"; 1129 private static final boolean DBG = false; 1130 1131 /* commands for Wifi Service */ 1132 private static final int BASE = Protocol.BASE_WIFI_SCANNER; 1133 1134 /** @hide */ 1135 public static final int CMD_SCAN = BASE + 0; 1136 /** @hide */ 1137 public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; 1138 /** @hide */ 1139 public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; 1140 /** @hide */ 1141 public static final int CMD_GET_SCAN_RESULTS = BASE + 4; 1142 /** @hide */ 1143 public static final int CMD_SCAN_RESULT = BASE + 5; 1144 /** @hide */ 1145 public static final int CMD_SET_HOTLIST = BASE + 6; 1146 /** @hide */ 1147 public static final int CMD_RESET_HOTLIST = BASE + 7; 1148 /** @hide */ 1149 public static final int CMD_AP_FOUND = BASE + 9; 1150 /** @hide */ 1151 public static final int CMD_AP_LOST = BASE + 10; 1152 /** @hide */ 1153 public static final int CMD_START_TRACKING_CHANGE = BASE + 11; 1154 /** @hide */ 1155 public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; 1156 /** @hide */ 1157 public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; 1158 /** @hide */ 1159 public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; 1160 /** @hide */ 1161 public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; 1162 /** @hide */ 1163 public static final int CMD_OP_SUCCEEDED = BASE + 17; 1164 /** @hide */ 1165 public static final int CMD_OP_FAILED = BASE + 18; 1166 /** @hide */ 1167 public static final int CMD_PERIOD_CHANGED = BASE + 19; 1168 /** @hide */ 1169 public static final int CMD_FULL_SCAN_RESULT = BASE + 20; 1170 /** @hide */ 1171 public static final int CMD_START_SINGLE_SCAN = BASE + 21; 1172 /** @hide */ 1173 public static final int CMD_STOP_SINGLE_SCAN = BASE + 22; 1174 /** @hide */ 1175 public static final int CMD_SINGLE_SCAN_COMPLETED = BASE + 23; 1176 /** @hide */ 1177 public static final int CMD_START_PNO_SCAN = BASE + 24; 1178 /** @hide */ 1179 public static final int CMD_STOP_PNO_SCAN = BASE + 25; 1180 /** @hide */ 1181 public static final int CMD_PNO_NETWORK_FOUND = BASE + 26; 1182 /** @hide */ 1183 public static final int CMD_REGISTER_SCAN_LISTENER = BASE + 27; 1184 /** @hide */ 1185 public static final int CMD_DEREGISTER_SCAN_LISTENER = BASE + 28; 1186 1187 private Context mContext; 1188 private IWifiScanner mService; 1189 1190 private static final int INVALID_KEY = 0; 1191 private int mListenerKey = 1; 1192 1193 private final SparseArray mListenerMap = new SparseArray(); 1194 private final Object mListenerMapLock = new Object(); 1195 1196 private AsyncChannel mAsyncChannel; 1197 private final Handler mInternalHandler; 1198 1199 /** 1200 * Create a new WifiScanner instance. 1201 * Applications will almost always want to use 1202 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 1203 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. 1204 * @param context the application context 1205 * @param service the Binder interface 1206 * @param looper the Looper used to deliver callbacks 1207 * @hide 1208 */ 1209 public WifiScanner(Context context, IWifiScanner service, Looper looper) { 1210 mContext = context; 1211 mService = service; 1212 1213 Messenger messenger = null; 1214 try { 1215 messenger = mService.getMessenger(); 1216 } catch (RemoteException e) { 1217 throw e.rethrowFromSystemServer(); 1218 } 1219 1220 if (messenger == null) { 1221 throw new IllegalStateException("getMessenger() returned null! This is invalid."); 1222 } 1223 1224 mAsyncChannel = new AsyncChannel(); 1225 1226 mInternalHandler = new ServiceHandler(looper); 1227 mAsyncChannel.connectSync(mContext, mInternalHandler, messenger); 1228 // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message 1229 // synchronously, which causes WifiScanningService to receive the wrong replyTo value. 1230 mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 1231 } 1232 1233 private void validateChannel() { 1234 if (mAsyncChannel == null) throw new IllegalStateException( 1235 "No permission to access and change wifi or a bad initialization"); 1236 } 1237 1238 // Add a listener into listener map. If the listener already exists, return INVALID_KEY and 1239 // send an error message to internal handler; Otherwise add the listener to the listener map and 1240 // return the key of the listener. 1241 private int addListener(ActionListener listener) { 1242 synchronized (mListenerMapLock) { 1243 boolean keyExists = (getListenerKey(listener) != INVALID_KEY); 1244 // Note we need to put the listener into listener map even if it's a duplicate as the 1245 // internal handler will need the key to find the listener. In case of duplicates, 1246 // removing duplicate key logic will be handled in internal handler. 1247 int key = putListener(listener); 1248 if (keyExists) { 1249 if (DBG) Log.d(TAG, "listener key already exists"); 1250 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST, 1251 "Outstanding request with same key not stopped yet"); 1252 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key, 1253 operationResult); 1254 message.sendToTarget(); 1255 return INVALID_KEY; 1256 } else { 1257 return key; 1258 } 1259 } 1260 } 1261 1262 private int putListener(Object listener) { 1263 if (listener == null) return INVALID_KEY; 1264 int key; 1265 synchronized (mListenerMapLock) { 1266 do { 1267 key = mListenerKey++; 1268 } while (key == INVALID_KEY); 1269 mListenerMap.put(key, listener); 1270 } 1271 return key; 1272 } 1273 1274 private Object getListener(int key) { 1275 if (key == INVALID_KEY) return null; 1276 synchronized (mListenerMapLock) { 1277 Object listener = mListenerMap.get(key); 1278 return listener; 1279 } 1280 } 1281 1282 private int getListenerKey(Object listener) { 1283 if (listener == null) return INVALID_KEY; 1284 synchronized (mListenerMapLock) { 1285 int index = mListenerMap.indexOfValue(listener); 1286 if (index == -1) { 1287 return INVALID_KEY; 1288 } else { 1289 return mListenerMap.keyAt(index); 1290 } 1291 } 1292 } 1293 1294 private Object removeListener(int key) { 1295 if (key == INVALID_KEY) return null; 1296 synchronized (mListenerMapLock) { 1297 Object listener = mListenerMap.get(key); 1298 mListenerMap.remove(key); 1299 return listener; 1300 } 1301 } 1302 1303 private int removeListener(Object listener) { 1304 int key = getListenerKey(listener); 1305 if (key == INVALID_KEY) { 1306 Log.e(TAG, "listener cannot be found"); 1307 return key; 1308 } 1309 synchronized (mListenerMapLock) { 1310 mListenerMap.remove(key); 1311 return key; 1312 } 1313 } 1314 1315 /** @hide */ 1316 public static class OperationResult implements Parcelable { 1317 public int reason; 1318 public String description; 1319 1320 public OperationResult(int reason, String description) { 1321 this.reason = reason; 1322 this.description = description; 1323 } 1324 1325 /** Implement the Parcelable interface {@hide} */ 1326 public int describeContents() { 1327 return 0; 1328 } 1329 1330 /** Implement the Parcelable interface {@hide} */ 1331 public void writeToParcel(Parcel dest, int flags) { 1332 dest.writeInt(reason); 1333 dest.writeString(description); 1334 } 1335 1336 /** Implement the Parcelable interface {@hide} */ 1337 public static final Creator<OperationResult> CREATOR = 1338 new Creator<OperationResult>() { 1339 public OperationResult createFromParcel(Parcel in) { 1340 int reason = in.readInt(); 1341 String description = in.readString(); 1342 return new OperationResult(reason, description); 1343 } 1344 1345 public OperationResult[] newArray(int size) { 1346 return new OperationResult[size]; 1347 } 1348 }; 1349 } 1350 1351 private class ServiceHandler extends Handler { 1352 ServiceHandler(Looper looper) { 1353 super(looper); 1354 } 1355 @Override 1356 public void handleMessage(Message msg) { 1357 switch (msg.what) { 1358 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 1359 return; 1360 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 1361 Log.e(TAG, "Channel connection lost"); 1362 // This will cause all further async API calls on the WifiManager 1363 // to fail and throw an exception 1364 mAsyncChannel = null; 1365 getLooper().quit(); 1366 return; 1367 } 1368 1369 Object listener = getListener(msg.arg2); 1370 1371 if (listener == null) { 1372 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2); 1373 return; 1374 } else { 1375 if (DBG) Log.d(TAG, "listener key = " + msg.arg2); 1376 } 1377 1378 switch (msg.what) { 1379 /* ActionListeners grouped together */ 1380 case CMD_OP_SUCCEEDED : 1381 ((ActionListener) listener).onSuccess(); 1382 break; 1383 case CMD_OP_FAILED : { 1384 OperationResult result = (OperationResult)msg.obj; 1385 ((ActionListener) listener).onFailure(result.reason, result.description); 1386 removeListener(msg.arg2); 1387 } 1388 break; 1389 case CMD_SCAN_RESULT : 1390 ((ScanListener) listener).onResults( 1391 ((ParcelableScanData) msg.obj).getResults()); 1392 return; 1393 case CMD_FULL_SCAN_RESULT : 1394 ScanResult result = (ScanResult) msg.obj; 1395 ((ScanListener) listener).onFullResult(result); 1396 return; 1397 case CMD_PERIOD_CHANGED: 1398 ((ScanListener) listener).onPeriodChanged(msg.arg1); 1399 return; 1400 case CMD_AP_FOUND: 1401 ((BssidListener) listener).onFound( 1402 ((ParcelableScanResults) msg.obj).getResults()); 1403 return; 1404 case CMD_AP_LOST: 1405 ((BssidListener) listener).onLost( 1406 ((ParcelableScanResults) msg.obj).getResults()); 1407 return; 1408 case CMD_WIFI_CHANGE_DETECTED: 1409 ((WifiChangeListener) listener).onChanging( 1410 ((ParcelableScanResults) msg.obj).getResults()); 1411 return; 1412 case CMD_WIFI_CHANGES_STABILIZED: 1413 ((WifiChangeListener) listener).onQuiescence( 1414 ((ParcelableScanResults) msg.obj).getResults()); 1415 return; 1416 case CMD_SINGLE_SCAN_COMPLETED: 1417 if (DBG) Log.d(TAG, "removing listener for single scan"); 1418 removeListener(msg.arg2); 1419 break; 1420 case CMD_PNO_NETWORK_FOUND: 1421 ((PnoScanListener) listener).onPnoNetworkFound( 1422 ((ParcelableScanResults) msg.obj).getResults()); 1423 return; 1424 default: 1425 if (DBG) Log.d(TAG, "Ignoring message " + msg.what); 1426 return; 1427 } 1428 } 1429 } 1430 } 1431