WifiScanner.java revision 011e1b35a64180d6f0234af8a3c2b70777eb9f39
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net.wifi; 18 19import android.content.Context; 20import android.os.Handler; 21import android.os.HandlerThread; 22import android.os.Looper; 23import android.os.Message; 24import android.os.Messenger; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.os.RemoteException; 28import android.util.Log; 29import android.util.SparseArray; 30 31import com.android.internal.util.AsyncChannel; 32import com.android.internal.util.Protocol; 33 34import java.util.ArrayList; 35import java.util.List; 36import java.util.concurrent.CountDownLatch; 37 38 39/** 40 * This class provides a way to scan the Wifi universe around the device 41 * Get an instance of this class by calling 42 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context 43 * .WIFI_SCANNING_SERVICE)}. 44 * @hide 45 */ 46public class WifiScanner { 47 48 public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */ 49 public static final int WIFI_BAND_24_GHZ = 1; /* 2.4 GHz band */ 50 public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */ 51 public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */ 52 public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */ 53 public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */ 54 public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ 55 56 public static final int MIN_SCAN_PERIOD_MS = 300; /* minimum supported period */ 57 public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ 58 59 public static final int REASON_SUCCEEDED = 0; 60 public static final int REASON_UNSPECIFIED = -1; 61 public static final int REASON_INVALID_LISTENER = -2; 62 public static final int REASON_INVALID_REQUEST = -3; 63 public static final int REASON_CONFLICTING_REQUEST = -4; 64 65 public static interface ActionListener { 66 public void onSuccess(Object result); 67 public void onFailure(int reason, Object exception); 68 } 69 70 /** 71 * gives you all the possible channels; channel is specified as an 72 * integer with frequency in MHz i.e. channel 1 is 2412 73 */ 74 public List<Integer> getAvailableChannels(int band) { 75 return null; 76 } 77 78 /** 79 * provides channel specification to the APIs 80 */ 81 public static class ChannelSpec { 82 public int frequency; 83 public boolean passive; /* ignored on DFS channels */ 84 public int dwellTimeMS; /* not supported for now */ 85 86 public ChannelSpec(int frequency) { 87 this.frequency = frequency; 88 passive = false; 89 dwellTimeMS = 0; 90 } 91 } 92 93 public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; 94 public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; 95 public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; 96 97 /** 98 * scan configuration parameters 99 */ 100 public static class ScanSettings implements Parcelable { 101 102 public int band; /* ignore channels if specified */ 103 public ChannelSpec[] channels; /* list of channels to scan */ 104 public int periodInMs; /* period of scan */ 105 public int reportEvents; /* a valid REPORT_EVENT value */ 106 107 /** Implement the Parcelable interface {@hide} */ 108 public int describeContents() { 109 return 0; 110 } 111 112 /** Implement the Parcelable interface {@hide} */ 113 public void writeToParcel(Parcel dest, int flags) { 114 dest.writeInt(band); 115 dest.writeInt(periodInMs); 116 dest.writeInt(channels.length); 117 118 for (int i = 0; i < channels.length; i++) { 119 dest.writeInt(channels[i].frequency); 120 dest.writeInt(channels[i].dwellTimeMS); 121 dest.writeInt(channels[i].passive ? 1 : 0); 122 } 123 } 124 125 /** Implement the Parcelable interface {@hide} */ 126 public static final Creator<ScanSettings> CREATOR = 127 new Creator<ScanSettings>() { 128 public ScanSettings createFromParcel(Parcel in) { 129 130 ScanSettings settings = new ScanSettings(); 131 settings.band = in.readInt(); 132 settings.periodInMs = in.readInt(); 133 int num_channels = in.readInt(); 134 settings.channels = new ChannelSpec[num_channels]; 135 for (int i = 0; i < num_channels; i++) { 136 int frequency = in.readInt(); 137 138 ChannelSpec spec = new ChannelSpec(frequency); 139 spec.dwellTimeMS = in.readInt(); 140 spec.passive = in.readInt() == 1; 141 settings.channels[i] = spec; 142 } 143 144 return settings; 145 } 146 147 public ScanSettings[] newArray(int size) { 148 return new ScanSettings[size]; 149 } 150 }; 151 152 } 153 154 public static class InformationElement { 155 public int id; 156 public byte[] bytes; 157 } 158 159 public static class FullScanResult { 160 public ScanResult result; 161 public InformationElement informationElements[]; 162 } 163 164 /** @hide */ 165 public static class ParcelableScanResults implements Parcelable { 166 public ScanResult mResults[]; 167 168 public ParcelableScanResults(ScanResult[] results) { 169 mResults = results; 170 } 171 172 public ScanResult[] getResults() { 173 return mResults; 174 } 175 176 /** Implement the Parcelable interface {@hide} */ 177 public int describeContents() { 178 return 0; 179 } 180 181 /** Implement the Parcelable interface {@hide} */ 182 public void writeToParcel(Parcel dest, int flags) { 183 dest.writeInt(mResults.length); 184 for (int i = 0; i < mResults.length; i++) { 185 ScanResult result = mResults[i]; 186 result.writeToParcel(dest, flags); 187 } 188 } 189 190 /** Implement the Parcelable interface {@hide} */ 191 public static final Creator<ParcelableScanResults> CREATOR = 192 new Creator<ParcelableScanResults>() { 193 public ParcelableScanResults createFromParcel(Parcel in) { 194 int n = in.readInt(); 195 ScanResult results[] = new ScanResult[n]; 196 for (int i = 0; i < n; i++) { 197 results[i] = ScanResult.CREATOR.createFromParcel(in); 198 } 199 return new ParcelableScanResults(results); 200 } 201 202 public ParcelableScanResults[] newArray(int size) { 203 return new ParcelableScanResults[size]; 204 } 205 }; 206 } 207 208 /** 209 * Framework is co-ordinating scans across multiple apps; so it may not give exactly the 210 * same period requested. The period granted is stated on the onSuccess() event; and 211 * onPeriodChanged() will be called if/when it is changed because of multiple conflicting 212 * requests. This is similar to the way timers are handled. 213 */ 214 public interface ScanListener extends ActionListener { 215 public void onPeriodChanged(int periodInMs); 216 public void onResults(ScanResult[] results); 217 public void onFullResult(FullScanResult fullScanResult); 218 } 219 220 public void scan(ScanSettings settings, ScanListener listener) { 221 validateChannel(); 222 sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings); 223 } 224 public void startBackgroundScan(ScanSettings settings, ScanListener listener) { 225 validateChannel(); 226 sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings); 227 } 228 public void stopBackgroundScan(boolean flush, ScanListener listener) { 229 validateChannel(); 230 sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener)); 231 } 232 public void retrieveScanResults(boolean flush, ScanListener listener) { 233 validateChannel(); 234 sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener)); 235 } 236 237 public static class HotspotInfo { 238 public String bssid; 239 public int low; /* minimum RSSI */ 240 public int high; /* maximum RSSI */ 241 } 242 243 public static class WifiChangeSettings { 244 public int rssiSampleSize; /* sample size for RSSI averaging */ 245 public int lostApSampleSize; /* samples to confirm AP's loss */ 246 public int unchangedSampleSize; /* samples to confirm no change */ 247 public int minApsBreachingThreshold; /* change threshold to trigger event */ 248 public HotspotInfo[] hotspotInfos; 249 } 250 251 /* overrides the significant wifi change state machine configuration */ 252 public void configureSignificantWifiChange( 253 int rssiSampleSize, /* sample size for RSSI averaging */ 254 int lostApSampleSize, /* samples to confirm AP's loss */ 255 int unchangedSampleSize, /* samples to confirm no change */ 256 int minApsBreachingThreshold, /* change threshold to trigger event */ 257 HotspotInfo[] hotspotInfos /* signal thresholds to crosss */ 258 ) 259 { 260 validateChannel(); 261 WifiChangeSettings settings = new WifiChangeSettings(); 262 settings.rssiSampleSize = rssiSampleSize; 263 settings.lostApSampleSize = lostApSampleSize; 264 settings.unchangedSampleSize = unchangedSampleSize; 265 settings.minApsBreachingThreshold = minApsBreachingThreshold; 266 settings.hotspotInfos = hotspotInfos; 267 268 sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); 269 } 270 271 public interface SignificantWifiChangeListener extends ActionListener { 272 public void onChanging(ScanResult[] results); /* changes are found */ 273 public void onQuiescence(ScanResult[] results); /* changes settled down */ 274 } 275 276 public void trackSignificantWifiChange(SignificantWifiChangeListener listener) { 277 validateChannel(); 278 sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener)); 279 } 280 public void untrackSignificantWifiChange(SignificantWifiChangeListener listener) { 281 validateChannel(); 282 sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener)); 283 } 284 285 public void configureSignificantWifiChange(WifiChangeSettings settings) { 286 validateChannel(); 287 sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); 288 } 289 290 public static interface HotlistListener extends ActionListener { 291 public void onFound(ScanResult[] results); 292 } 293 294 /** @hide */ 295 public static class HotlistSettings implements Parcelable { 296 public HotspotInfo[] hotspotInfos; 297 public int apLostThreshold; 298 299 /** Implement the Parcelable interface {@hide} */ 300 public int describeContents() { 301 return 0; 302 } 303 304 /** Implement the Parcelable interface {@hide} */ 305 public void writeToParcel(Parcel dest, int flags) { 306 dest.writeInt(apLostThreshold); 307 dest.writeInt(hotspotInfos.length); 308 for (int i = 0; i < hotspotInfos.length; i++) { 309 HotspotInfo info = hotspotInfos[i]; 310 dest.writeString(info.bssid); 311 dest.writeInt(info.low); 312 dest.writeInt(info.high); 313 } 314 } 315 316 /** Implement the Parcelable interface {@hide} */ 317 public static final Creator<HotlistSettings> CREATOR = 318 new Creator<HotlistSettings>() { 319 public HotlistSettings createFromParcel(Parcel in) { 320 HotlistSettings settings = new HotlistSettings(); 321 settings.apLostThreshold = in.readInt(); 322 int n = in.readInt(); 323 settings.hotspotInfos = new HotspotInfo[n]; 324 for (int i = 0; i < n; i++) { 325 HotspotInfo info = new HotspotInfo(); 326 info.bssid = in.readString(); 327 info.low = in.readInt(); 328 info.high = in.readInt(); 329 settings.hotspotInfos[i] = info; 330 } 331 return settings; 332 } 333 334 public HotlistSettings[] newArray(int size) { 335 return new HotlistSettings[size]; 336 } 337 }; 338 } 339 340 public void setHotlist(HotspotInfo[] hotspots, 341 int apLostThreshold, HotlistListener listener) { 342 validateChannel(); 343 HotlistSettings settings = new HotlistSettings(); 344 settings.hotspotInfos = hotspots; 345 sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings); 346 } 347 348 public void resetHotlist(HotlistListener listener) { 349 validateChannel(); 350 sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); 351 } 352 353 354 /* private members and methods */ 355 356 private static final String TAG = "WifiScanner"; 357 private static final boolean DBG = true; 358 359 /* commands for Wifi Service */ 360 private static final int BASE = Protocol.BASE_WIFI_SCANNER; 361 362 /** @hide */ 363 public static final int CMD_SCAN = BASE + 0; 364 /** @hide */ 365 public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; 366 /** @hide */ 367 public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; 368 /** @hide */ 369 public static final int CMD_GET_SCAN_RESULTS = BASE + 4; 370 /** @hide */ 371 public static final int CMD_SCAN_RESULT = BASE + 5; 372 /** @hide */ 373 public static final int CMD_SET_HOTLIST = BASE + 6; 374 /** @hide */ 375 public static final int CMD_RESET_HOTLIST = BASE + 7; 376 /** @hide */ 377 public static final int CMD_AP_FOUND = BASE + 9; 378 /** @hide */ 379 public static final int CMD_AP_LOST = BASE + 10; 380 /** @hide */ 381 public static final int CMD_START_TRACKING_CHANGE = BASE + 11; 382 /** @hide */ 383 public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; 384 /** @hide */ 385 public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; 386 /** @hide */ 387 public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; 388 /** @hide */ 389 public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; 390 /** @hide */ 391 public static final int CMD_OP_SUCCEEDED = BASE + 17; 392 /** @hide */ 393 public static final int CMD_OP_FAILED = BASE + 18; 394 /** @hide */ 395 public static final int CMD_PERIOD_CHANGED = BASE + 19; 396 /** @hide */ 397 public static final int CMD_FULL_SCAN_RESULT = BASE + 20; 398 399 private Context mContext; 400 private IWifiScanner mService; 401 402 private static final int INVALID_KEY = 0; 403 private static int sListenerKey = 1; 404 405 private static final SparseArray sListenerMap = new SparseArray(); 406 private static final Object sListenerMapLock = new Object(); 407 408 private static AsyncChannel sAsyncChannel; 409 private static CountDownLatch sConnected; 410 411 private static final Object sThreadRefLock = new Object(); 412 private static int sThreadRefCount; 413 private static HandlerThread sHandlerThread; 414 415 /** 416 * Create a new WifiScanner instance. 417 * Applications will almost always want to use 418 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 419 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. 420 * @param context the application context 421 * @param service the Binder interface 422 * @hide 423 */ 424 public WifiScanner(Context context, IWifiScanner service) { 425 mContext = context; 426 mService = service; 427 init(); 428 } 429 430 private void init() { 431 synchronized (sThreadRefLock) { 432 if (++sThreadRefCount == 1) { 433 Messenger messenger = null; 434 try { 435 messenger = mService.getMessenger(); 436 } catch (RemoteException e) { 437 /* do nothing */ 438 } catch (SecurityException e) { 439 /* do nothing */ 440 } 441 442 if (messenger == null) { 443 sAsyncChannel = null; 444 return; 445 } 446 447 sHandlerThread = new HandlerThread("WifiScanner"); 448 sAsyncChannel = new AsyncChannel(); 449 sConnected = new CountDownLatch(1); 450 451 sHandlerThread.start(); 452 Handler handler = new ServiceHandler(sHandlerThread.getLooper()); 453 sAsyncChannel.connect(mContext, handler, messenger); 454 try { 455 sConnected.await(); 456 } catch (InterruptedException e) { 457 Log.e(TAG, "interrupted wait at init"); 458 } 459 } 460 } 461 } 462 463 private void validateChannel() { 464 if (sAsyncChannel == null) throw new IllegalStateException( 465 "No permission to access and change wifi or a bad initialization"); 466 } 467 468 private static int putListener(Object listener) { 469 if (listener == null) return INVALID_KEY; 470 int key; 471 synchronized (sListenerMapLock) { 472 do { 473 key = sListenerKey++; 474 } while (key == INVALID_KEY); 475 sListenerMap.put(key, listener); 476 } 477 return key; 478 } 479 480 private static Object getListener(int key) { 481 if (key == INVALID_KEY) return null; 482 synchronized (sListenerMapLock) { 483 Object listener = sListenerMap.get(key); 484 return listener; 485 } 486 } 487 488 private static int getListenerKey(Object listener) { 489 if (listener == null) return INVALID_KEY; 490 synchronized (sListenerMapLock) { 491 int index = sListenerMap.indexOfValue(listener); 492 if (index == -1) { 493 return INVALID_KEY; 494 } else { 495 return sListenerMap.keyAt(index); 496 } 497 } 498 } 499 500 private static Object removeListener(int key) { 501 if (key == INVALID_KEY) return null; 502 synchronized (sListenerMapLock) { 503 Object listener = sListenerMap.get(key); 504 sListenerMap.remove(key); 505 return listener; 506 } 507 } 508 509 private static int removeListener(Object listener) { 510 int key = getListenerKey(listener); 511 if (key == INVALID_KEY) return key; 512 synchronized (sListenerMapLock) { 513 sListenerMap.remove(key); 514 return key; 515 } 516 } 517 518 private static class ServiceHandler extends Handler { 519 ServiceHandler(Looper looper) { 520 super(looper); 521 } 522 @Override 523 public void handleMessage(Message msg) { 524 switch (msg.what) { 525 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 526 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 527 sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); 528 } else { 529 Log.e(TAG, "Failed to set up channel connection"); 530 // This will cause all further async API calls on the WifiManager 531 // to fail and throw an exception 532 sAsyncChannel = null; 533 } 534 sConnected.countDown(); 535 return; 536 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: 537 return; 538 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 539 Log.e(TAG, "Channel connection lost"); 540 // This will cause all further async API calls on the WifiManager 541 // to fail and throw an exception 542 sAsyncChannel = null; 543 getLooper().quit(); 544 return; 545 } 546 547 Object listener = getListener(msg.arg2); 548 if (DBG) Log.d(TAG, "listener key = " + msg.arg2); 549 550 switch (msg.what) { 551 /* ActionListeners grouped together */ 552 case CMD_OP_SUCCEEDED : 553 ((ActionListener) listener).onSuccess(msg.obj); 554 break; 555 case CMD_OP_FAILED : 556 ((ActionListener) listener).onFailure(msg.arg1, msg.obj); 557 break; 558 case CMD_SCAN_RESULT : 559 ((ScanListener) listener).onResults( 560 ((ParcelableScanResults) msg.obj).getResults()); 561 return; 562 case CMD_FULL_SCAN_RESULT : 563 FullScanResult result = (FullScanResult) msg.obj; 564 ((ScanListener) listener).onFullResult(result); 565 return; 566 case CMD_AP_FOUND: 567 ((HotlistListener) listener).onFound( 568 ((ParcelableScanResults) msg.obj).getResults()); 569 return; 570 case CMD_WIFI_CHANGE_DETECTED: 571 ((SignificantWifiChangeListener) listener).onChanging( 572 ((ParcelableScanResults) msg.obj).getResults()); 573 return; 574 case CMD_WIFI_CHANGES_STABILIZED: 575 ((SignificantWifiChangeListener) listener).onQuiescence( 576 ((ParcelableScanResults) msg.obj).getResults()); 577 return; 578 default: 579 if (DBG) Log.d(TAG, "Ignoring message " + msg.what); 580 return; 581 } 582 } 583 } 584} 585