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 { 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 /** Implement the Parcelable interface {@hide} */ 163 public int describeContents() { 164 return 0; 165 } 166 167 /** Implement the Parcelable interface {@hide} */ 168 public void writeToParcel(Parcel dest, int flags) { 169 dest.writeInt(band); 170 dest.writeInt(periodInMs); 171 dest.writeInt(reportEvents); 172 dest.writeInt(numBssidsPerScan); 173 174 if (channels != null) { 175 dest.writeInt(channels.length); 176 177 for (int i = 0; i < channels.length; i++) { 178 dest.writeInt(channels[i].frequency); 179 dest.writeInt(channels[i].dwellTimeMS); 180 dest.writeInt(channels[i].passive ? 1 : 0); 181 } 182 } else { 183 dest.writeInt(0); 184 } 185 } 186 187 /** Implement the Parcelable interface {@hide} */ 188 public static final Creator<ScanSettings> CREATOR = 189 new Creator<ScanSettings>() { 190 public ScanSettings createFromParcel(Parcel in) { 191 192 ScanSettings settings = new ScanSettings(); 193 settings.band = in.readInt(); 194 settings.periodInMs = in.readInt(); 195 settings.reportEvents = in.readInt(); 196 settings.numBssidsPerScan = in.readInt(); 197 int num_channels = in.readInt(); 198 settings.channels = new ChannelSpec[num_channels]; 199 for (int i = 0; i < num_channels; i++) { 200 int frequency = in.readInt(); 201 202 ChannelSpec spec = new ChannelSpec(frequency); 203 spec.dwellTimeMS = in.readInt(); 204 spec.passive = in.readInt() == 1; 205 settings.channels[i] = spec; 206 } 207 208 return settings; 209 } 210 211 public ScanSettings[] newArray(int size) { 212 return new ScanSettings[size]; 213 } 214 }; 215 216 } 217 218 /** @hide */ 219 public static class ParcelableScanResults implements Parcelable { 220 public ScanResult mResults[]; 221 222 public ParcelableScanResults(ScanResult[] results) { 223 mResults = results; 224 } 225 226 public ScanResult[] getResults() { 227 return mResults; 228 } 229 230 /** Implement the Parcelable interface {@hide} */ 231 public int describeContents() { 232 return 0; 233 } 234 235 /** Implement the Parcelable interface {@hide} */ 236 public void writeToParcel(Parcel dest, int flags) { 237 if (mResults != null) { 238 dest.writeInt(mResults.length); 239 for (int i = 0; i < mResults.length; i++) { 240 ScanResult result = mResults[i]; 241 result.writeToParcel(dest, flags); 242 } 243 } else { 244 dest.writeInt(0); 245 } 246 } 247 248 /** Implement the Parcelable interface {@hide} */ 249 public static final Creator<ParcelableScanResults> CREATOR = 250 new Creator<ParcelableScanResults>() { 251 public ParcelableScanResults createFromParcel(Parcel in) { 252 int n = in.readInt(); 253 ScanResult results[] = new ScanResult[n]; 254 for (int i = 0; i < n; i++) { 255 results[i] = ScanResult.CREATOR.createFromParcel(in); 256 } 257 return new ParcelableScanResults(results); 258 } 259 260 public ParcelableScanResults[] newArray(int size) { 261 return new ParcelableScanResults[size]; 262 } 263 }; 264 } 265 266 /** 267 * interface to get scan events on; specify this on {@link #startBackgroundScan} 268 */ 269 public interface ScanListener extends ActionListener { 270 /** 271 * Framework co-ordinates scans across multiple apps; so it may not give exactly the 272 * same period requested. If period of a scan is changed; it is reported by this event. 273 */ 274 public void onPeriodChanged(int periodInMs); 275 /** 276 * reports results retrieved from background scan 277 */ 278 public void onResults(ScanResult[] results); 279 /** 280 * reports full scan result for each access point found in scan 281 */ 282 public void onFullResult(ScanResult fullScanResult); 283 } 284 285 /** start wifi scan in background 286 * @param settings specifies various parameters for the scan; for more information look at 287 * {@link ScanSettings} 288 * @param listener specifies the object to report events to. This object is also treated as a 289 * key for this scan, and must also be specified to cancel the scan. Multiple 290 * scans should also not share this object. 291 */ 292 public void startBackgroundScan(ScanSettings settings, ScanListener listener) { 293 validateChannel(); 294 sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings); 295 } 296 /** 297 * stop an ongoing wifi scan 298 * @param listener specifies which scan to cancel; must be same object as passed in {@link 299 * #startBackgroundScan} 300 */ 301 public void stopBackgroundScan(ScanListener listener) { 302 validateChannel(); 303 sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener)); 304 } 305 /** 306 * retrieves currently available scan results 307 */ 308 public ScanResult[] getScanResults() { 309 validateChannel(); 310 Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0); 311 ScanResult[] results = (ScanResult[]) reply.obj; 312 return results; 313 } 314 315 /** specifies information about an access point of interest */ 316 public static class BssidInfo { 317 /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */ 318 public String bssid; 319 /** low signal strength threshold; more information at {@link ScanResult#level} */ 320 public int low; /* minimum RSSI */ 321 /** high signal threshold; more information at {@link ScanResult#level} */ 322 public int high; /* maximum RSSI */ 323 /** channel frequency (in KHz) where you may find this BSSID */ 324 public int frequencyHint; 325 } 326 327 /** @hide */ 328 @SystemApi 329 public static class WifiChangeSettings implements Parcelable { 330 public int rssiSampleSize; /* sample size for RSSI averaging */ 331 public int lostApSampleSize; /* samples to confirm AP's loss */ 332 public int unchangedSampleSize; /* samples to confirm no change */ 333 public int minApsBreachingThreshold; /* change threshold to trigger event */ 334 public int periodInMs; /* scan period in millisecond */ 335 public BssidInfo[] bssidInfos; 336 337 /** Implement the Parcelable interface {@hide} */ 338 public int describeContents() { 339 return 0; 340 } 341 342 /** Implement the Parcelable interface {@hide} */ 343 public void writeToParcel(Parcel dest, int flags) { 344 dest.writeInt(rssiSampleSize); 345 dest.writeInt(lostApSampleSize); 346 dest.writeInt(unchangedSampleSize); 347 dest.writeInt(minApsBreachingThreshold); 348 dest.writeInt(periodInMs); 349 if (bssidInfos != null) { 350 dest.writeInt(bssidInfos.length); 351 for (int i = 0; i < bssidInfos.length; i++) { 352 BssidInfo info = bssidInfos[i]; 353 dest.writeString(info.bssid); 354 dest.writeInt(info.low); 355 dest.writeInt(info.high); 356 dest.writeInt(info.frequencyHint); 357 } 358 } else { 359 dest.writeInt(0); 360 } 361 } 362 363 /** Implement the Parcelable interface {@hide} */ 364 public static final Creator<WifiChangeSettings> CREATOR = 365 new Creator<WifiChangeSettings>() { 366 public WifiChangeSettings createFromParcel(Parcel in) { 367 WifiChangeSettings settings = new WifiChangeSettings(); 368 settings.rssiSampleSize = in.readInt(); 369 settings.lostApSampleSize = in.readInt(); 370 settings.unchangedSampleSize = in.readInt(); 371 settings.minApsBreachingThreshold = in.readInt(); 372 settings.periodInMs = in.readInt(); 373 int len = in.readInt(); 374 settings.bssidInfos = new BssidInfo[len]; 375 for (int i = 0; i < len; i++) { 376 BssidInfo info = new BssidInfo(); 377 info.bssid = in.readString(); 378 info.low = in.readInt(); 379 info.high = in.readInt(); 380 info.frequencyHint = in.readInt(); 381 settings.bssidInfos[i] = info; 382 } 383 return settings; 384 } 385 386 public WifiChangeSettings[] newArray(int size) { 387 return new WifiChangeSettings[size]; 388 } 389 }; 390 391 } 392 393 /** configure WifiChange detection 394 * @param rssiSampleSize number of samples used for RSSI averaging 395 * @param lostApSampleSize number of samples to confirm an access point's loss 396 * @param unchangedSampleSize number of samples to confirm there are no changes 397 * @param minApsBreachingThreshold minimum number of access points that need to be 398 * out of range to detect WifiChange 399 * @param periodInMs indicates period of scan to find changes 400 * @param bssidInfos access points to watch 401 */ 402 public void configureWifiChange( 403 int rssiSampleSize, /* sample size for RSSI averaging */ 404 int lostApSampleSize, /* samples to confirm AP's loss */ 405 int unchangedSampleSize, /* samples to confirm no change */ 406 int minApsBreachingThreshold, /* change threshold to trigger event */ 407 int periodInMs, /* period of scan */ 408 BssidInfo[] bssidInfos /* signal thresholds to crosss */ 409 ) 410 { 411 validateChannel(); 412 413 WifiChangeSettings settings = new WifiChangeSettings(); 414 settings.rssiSampleSize = rssiSampleSize; 415 settings.lostApSampleSize = lostApSampleSize; 416 settings.unchangedSampleSize = unchangedSampleSize; 417 settings.minApsBreachingThreshold = minApsBreachingThreshold; 418 settings.periodInMs = periodInMs; 419 settings.bssidInfos = bssidInfos; 420 421 configureWifiChange(settings); 422 } 423 424 /** 425 * interface to get wifi change events on; use this on {@link #startTrackingWifiChange} 426 */ 427 public interface WifiChangeListener extends ActionListener { 428 /** indicates that changes were detected in wifi environment 429 * @param results indicate the access points that exhibited change 430 */ 431 public void onChanging(ScanResult[] results); /* changes are found */ 432 /** indicates that no wifi changes are being detected for a while 433 * @param results indicate the access points that are bing monitored for change 434 */ 435 public void onQuiescence(ScanResult[] results); /* changes settled down */ 436 } 437 438 /** 439 * track changes in wifi environment 440 * @param listener object to report events on; this object must be unique and must also be 441 * provided on {@link #stopTrackingWifiChange} 442 */ 443 public void startTrackingWifiChange(WifiChangeListener listener) { 444 validateChannel(); 445 sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener)); 446 } 447 448 /** 449 * stop tracking changes in wifi environment 450 * @param listener object that was provided to report events on {@link 451 * #stopTrackingWifiChange} 452 */ 453 public void stopTrackingWifiChange(WifiChangeListener listener) { 454 validateChannel(); 455 sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener)); 456 } 457 458 /** @hide */ 459 @SystemApi 460 public void configureWifiChange(WifiChangeSettings settings) { 461 validateChannel(); 462 sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); 463 } 464 465 /** interface to receive hotlist events on; use this on {@link #setHotlist} */ 466 public static interface BssidListener extends ActionListener { 467 /** indicates that access points were found by on going scans 468 * @param results list of scan results, one for each access point visible currently 469 */ 470 public void onFound(ScanResult[] results); 471 } 472 473 /** @hide */ 474 @SystemApi 475 public static class HotlistSettings implements Parcelable { 476 public BssidInfo[] bssidInfos; 477 public int apLostThreshold; 478 479 /** Implement the Parcelable interface {@hide} */ 480 public int describeContents() { 481 return 0; 482 } 483 484 /** Implement the Parcelable interface {@hide} */ 485 public void writeToParcel(Parcel dest, int flags) { 486 dest.writeInt(apLostThreshold); 487 488 if (bssidInfos != null) { 489 dest.writeInt(bssidInfos.length); 490 for (int i = 0; i < bssidInfos.length; i++) { 491 BssidInfo info = bssidInfos[i]; 492 dest.writeString(info.bssid); 493 dest.writeInt(info.low); 494 dest.writeInt(info.high); 495 dest.writeInt(info.frequencyHint); 496 } 497 } else { 498 dest.writeInt(0); 499 } 500 } 501 502 /** Implement the Parcelable interface {@hide} */ 503 public static final Creator<HotlistSettings> CREATOR = 504 new Creator<HotlistSettings>() { 505 public HotlistSettings createFromParcel(Parcel in) { 506 HotlistSettings settings = new HotlistSettings(); 507 settings.apLostThreshold = in.readInt(); 508 int n = in.readInt(); 509 settings.bssidInfos = new BssidInfo[n]; 510 for (int i = 0; i < n; i++) { 511 BssidInfo info = new BssidInfo(); 512 info.bssid = in.readString(); 513 info.low = in.readInt(); 514 info.high = in.readInt(); 515 info.frequencyHint = in.readInt(); 516 settings.bssidInfos[i] = info; 517 } 518 return settings; 519 } 520 521 public HotlistSettings[] newArray(int size) { 522 return new HotlistSettings[size]; 523 } 524 }; 525 } 526 527 /** 528 * set interesting access points to find 529 * @param bssidInfos access points of interest 530 * @param apLostThreshold number of scans needed to indicate that AP is lost 531 * @param listener object provided to report events on; this object must be unique and must 532 * also be provided on {@link #stopTrackingBssids} 533 */ 534 public void startTrackingBssids(BssidInfo[] bssidInfos, 535 int apLostThreshold, BssidListener listener) { 536 validateChannel(); 537 HotlistSettings settings = new HotlistSettings(); 538 settings.bssidInfos = bssidInfos; 539 sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings); 540 } 541 542 /** 543 * remove tracking of interesting access points 544 * @param listener same object provided in {@link #startTrackingBssids} 545 */ 546 public void stopTrackingBssids(BssidListener listener) { 547 validateChannel(); 548 sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); 549 } 550 551 552 /* private members and methods */ 553 554 private static final String TAG = "WifiScanner"; 555 private static final boolean DBG = true; 556 557 /* commands for Wifi Service */ 558 private static final int BASE = Protocol.BASE_WIFI_SCANNER; 559 560 /** @hide */ 561 public static final int CMD_SCAN = BASE + 0; 562 /** @hide */ 563 public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; 564 /** @hide */ 565 public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; 566 /** @hide */ 567 public static final int CMD_GET_SCAN_RESULTS = BASE + 4; 568 /** @hide */ 569 public static final int CMD_SCAN_RESULT = BASE + 5; 570 /** @hide */ 571 public static final int CMD_SET_HOTLIST = BASE + 6; 572 /** @hide */ 573 public static final int CMD_RESET_HOTLIST = BASE + 7; 574 /** @hide */ 575 public static final int CMD_AP_FOUND = BASE + 9; 576 /** @hide */ 577 public static final int CMD_AP_LOST = BASE + 10; 578 /** @hide */ 579 public static final int CMD_START_TRACKING_CHANGE = BASE + 11; 580 /** @hide */ 581 public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; 582 /** @hide */ 583 public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; 584 /** @hide */ 585 public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; 586 /** @hide */ 587 public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; 588 /** @hide */ 589 public static final int CMD_OP_SUCCEEDED = BASE + 17; 590 /** @hide */ 591 public static final int CMD_OP_FAILED = BASE + 18; 592 /** @hide */ 593 public static final int CMD_PERIOD_CHANGED = BASE + 19; 594 /** @hide */ 595 public static final int CMD_FULL_SCAN_RESULT = BASE + 20; 596 597 private Context mContext; 598 private IWifiScanner mService; 599 600 private static final int INVALID_KEY = 0; 601 private static int sListenerKey = 1; 602 603 private static final SparseArray sListenerMap = new SparseArray(); 604 private static final Object sListenerMapLock = new Object(); 605 606 private static AsyncChannel sAsyncChannel; 607 private static CountDownLatch sConnected; 608 609 private static final Object sThreadRefLock = new Object(); 610 private static int sThreadRefCount; 611 private static HandlerThread sHandlerThread; 612 613 /** 614 * Create a new WifiScanner instance. 615 * Applications will almost always want to use 616 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 617 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. 618 * @param context the application context 619 * @param service the Binder interface 620 * @hide 621 */ 622 public WifiScanner(Context context, IWifiScanner service) { 623 mContext = context; 624 mService = service; 625 init(); 626 } 627 628 private void init() { 629 synchronized (sThreadRefLock) { 630 if (++sThreadRefCount == 1) { 631 Messenger messenger = null; 632 try { 633 messenger = mService.getMessenger(); 634 } catch (RemoteException e) { 635 /* do nothing */ 636 } catch (SecurityException e) { 637 /* do nothing */ 638 } 639 640 if (messenger == null) { 641 sAsyncChannel = null; 642 return; 643 } 644 645 sHandlerThread = new HandlerThread("WifiScanner"); 646 sAsyncChannel = new AsyncChannel(); 647 sConnected = new CountDownLatch(1); 648 649 sHandlerThread.start(); 650 Handler handler = new ServiceHandler(sHandlerThread.getLooper()); 651 sAsyncChannel.connect(mContext, handler, messenger); 652 try { 653 sConnected.await(); 654 } catch (InterruptedException e) { 655 Log.e(TAG, "interrupted wait at init"); 656 } 657 } 658 } 659 } 660 661 private void validateChannel() { 662 if (sAsyncChannel == null) throw new IllegalStateException( 663 "No permission to access and change wifi or a bad initialization"); 664 } 665 666 private static int putListener(Object listener) { 667 if (listener == null) return INVALID_KEY; 668 int key; 669 synchronized (sListenerMapLock) { 670 do { 671 key = sListenerKey++; 672 } while (key == INVALID_KEY); 673 sListenerMap.put(key, listener); 674 } 675 return key; 676 } 677 678 private static Object getListener(int key) { 679 if (key == INVALID_KEY) return null; 680 synchronized (sListenerMapLock) { 681 Object listener = sListenerMap.get(key); 682 return listener; 683 } 684 } 685 686 private static int getListenerKey(Object listener) { 687 if (listener == null) return INVALID_KEY; 688 synchronized (sListenerMapLock) { 689 int index = sListenerMap.indexOfValue(listener); 690 if (index == -1) { 691 return INVALID_KEY; 692 } else { 693 return sListenerMap.keyAt(index); 694 } 695 } 696 } 697 698 private static Object removeListener(int key) { 699 if (key == INVALID_KEY) return null; 700 synchronized (sListenerMapLock) { 701 Object listener = sListenerMap.get(key); 702 sListenerMap.remove(key); 703 return listener; 704 } 705 } 706 707 private static int removeListener(Object listener) { 708 int key = getListenerKey(listener); 709 if (key == INVALID_KEY) return key; 710 synchronized (sListenerMapLock) { 711 sListenerMap.remove(key); 712 return key; 713 } 714 } 715 716 /** @hide */ 717 public static class OperationResult implements Parcelable { 718 public int reason; 719 public String description; 720 721 public OperationResult(int reason, String description) { 722 this.reason = reason; 723 this.description = description; 724 } 725 726 /** Implement the Parcelable interface {@hide} */ 727 public int describeContents() { 728 return 0; 729 } 730 731 /** Implement the Parcelable interface {@hide} */ 732 public void writeToParcel(Parcel dest, int flags) { 733 dest.writeInt(reason); 734 dest.writeString(description); 735 } 736 737 /** Implement the Parcelable interface {@hide} */ 738 public static final Creator<OperationResult> CREATOR = 739 new Creator<OperationResult>() { 740 public OperationResult createFromParcel(Parcel in) { 741 int reason = in.readInt(); 742 String description = in.readString(); 743 return new OperationResult(reason, description); 744 } 745 746 public OperationResult[] newArray(int size) { 747 return new OperationResult[size]; 748 } 749 }; 750 } 751 752 private static class ServiceHandler extends Handler { 753 ServiceHandler(Looper looper) { 754 super(looper); 755 } 756 @Override 757 public void handleMessage(Message msg) { 758 switch (msg.what) { 759 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 760 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 761 sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 762 } else { 763 Log.e(TAG, "Failed to set up channel connection"); 764 // This will cause all further async API calls on the WifiManager 765 // to fail and throw an exception 766 sAsyncChannel = null; 767 } 768 sConnected.countDown(); 769 return; 770 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 771 return; 772 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 773 Log.e(TAG, "Channel connection lost"); 774 // This will cause all further async API calls on the WifiManager 775 // to fail and throw an exception 776 sAsyncChannel = null; 777 getLooper().quit(); 778 return; 779 } 780 781 Object listener = getListener(msg.arg2); 782 783 if (listener == null) { 784 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2); 785 return; 786 } else { 787 if (DBG) Log.d(TAG, "listener key = " + msg.arg2); 788 } 789 790 switch (msg.what) { 791 /* ActionListeners grouped together */ 792 case CMD_OP_SUCCEEDED : 793 ((ActionListener) listener).onSuccess(); 794 break; 795 case CMD_OP_FAILED : { 796 OperationResult result = (OperationResult)msg.obj; 797 ((ActionListener) listener).onFailure(result.reason, result.description); 798 removeListener(msg.arg2); 799 } 800 break; 801 case CMD_SCAN_RESULT : 802 ((ScanListener) listener).onResults( 803 ((ParcelableScanResults) msg.obj).getResults()); 804 return; 805 case CMD_FULL_SCAN_RESULT : 806 ScanResult result = (ScanResult) msg.obj; 807 ((ScanListener) listener).onFullResult(result); 808 return; 809 case CMD_PERIOD_CHANGED: 810 ((ScanListener) listener).onPeriodChanged(msg.arg1); 811 return; 812 case CMD_AP_FOUND: 813 ((BssidListener) listener).onFound( 814 ((ParcelableScanResults) msg.obj).getResults()); 815 return; 816 case CMD_WIFI_CHANGE_DETECTED: 817 ((WifiChangeListener) listener).onChanging( 818 ((ParcelableScanResults) msg.obj).getResults()); 819 return; 820 case CMD_WIFI_CHANGES_STABILIZED: 821 ((WifiChangeListener) listener).onQuiescence( 822 ((ParcelableScanResults) msg.obj).getResults()); 823 return; 824 default: 825 if (DBG) Log.d(TAG, "Ignoring message " + msg.what); 826 return; 827 } 828 } 829 } 830 } 831