1/* 2 * Copyright (C) 2015 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 com.android.server.wifi.scanner; 18 19import android.app.AlarmManager; 20import android.content.Context; 21import android.net.wifi.ScanResult; 22import android.net.wifi.WifiScanner; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.Message; 26import android.util.Log; 27 28import com.android.internal.R; 29import com.android.server.wifi.Clock; 30import com.android.server.wifi.ScanDetail; 31import com.android.server.wifi.WifiMonitor; 32import com.android.server.wifi.WifiNative; 33import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; 34import com.android.server.wifi.util.ScanResultUtil; 35 36import java.io.FileDescriptor; 37import java.io.PrintWriter; 38import java.util.ArrayList; 39import java.util.Collections; 40import java.util.HashSet; 41import java.util.List; 42import java.util.Set; 43import java.util.stream.Collectors; 44 45import javax.annotation.concurrent.GuardedBy; 46 47/** 48 * Implementation of the WifiScanner HAL API that uses wificond to perform all scans 49 * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method. 50 */ 51public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback { 52 private static final String TAG = "WificondScannerImpl"; 53 private static final boolean DBG = false; 54 55 public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout"; 56 // Max number of networks that can be specified to wificond per scan request 57 public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16; 58 59 private static final int SCAN_BUFFER_CAPACITY = 10; 60 private static final int MAX_APS_PER_SCAN = 32; 61 private static final int MAX_SCAN_BUCKETS = 16; 62 63 private final Context mContext; 64 private final String mIfaceName; 65 private final WifiNative mWifiNative; 66 private final WifiMonitor mWifiMonitor; 67 private final AlarmManager mAlarmManager; 68 private final Handler mEventHandler; 69 private final ChannelHelper mChannelHelper; 70 private final Clock mClock; 71 72 private final Object mSettingsLock = new Object(); 73 74 private ArrayList<ScanDetail> mNativeScanResults; 75 private ArrayList<ScanDetail> mNativePnoScanResults; 76 private WifiScanner.ScanData mLatestSingleScanResult = 77 new WifiScanner.ScanData(0, 0, new ScanResult[0]); 78 79 // Settings for the currently running single scan, null if no scan active 80 private LastScanSettings mLastScanSettings = null; 81 // Settings for the currently running pno scan, null if no scan active 82 private LastPnoScanSettings mLastPnoScanSettings = null; 83 84 private final boolean mHwPnoScanSupported; 85 86 /** 87 * Duration to wait before timing out a scan. 88 * 89 * The expected behavior is that the hardware will return a failed scan if it does not 90 * complete, but timeout just in case it does not. 91 */ 92 private static final long SCAN_TIMEOUT_MS = 15000; 93 94 @GuardedBy("mSettingsLock") 95 private AlarmManager.OnAlarmListener mScanTimeoutListener; 96 97 public WificondScannerImpl(Context context, String ifaceName, WifiNative wifiNative, 98 WifiMonitor wifiMonitor, ChannelHelper channelHelper, 99 Looper looper, Clock clock) { 100 mContext = context; 101 mIfaceName = ifaceName; 102 mWifiNative = wifiNative; 103 mWifiMonitor = wifiMonitor; 104 mChannelHelper = channelHelper; 105 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 106 mEventHandler = new Handler(looper, this); 107 mClock = clock; 108 109 // Check if the device supports HW PNO scans. 110 mHwPnoScanSupported = mContext.getResources().getBoolean( 111 R.bool.config_wifi_background_scan_support); 112 113 wifiMonitor.registerHandler(mIfaceName, 114 WifiMonitor.SCAN_FAILED_EVENT, mEventHandler); 115 wifiMonitor.registerHandler(mIfaceName, 116 WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler); 117 wifiMonitor.registerHandler(mIfaceName, 118 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler); 119 } 120 121 @Override 122 public void cleanup() { 123 synchronized (mSettingsLock) { 124 stopHwPnoScan(); 125 mLastScanSettings = null; // finally clear any active scan 126 mLastPnoScanSettings = null; // finally clear any active scan 127 mWifiMonitor.deregisterHandler(mIfaceName, 128 WifiMonitor.SCAN_FAILED_EVENT, mEventHandler); 129 mWifiMonitor.deregisterHandler(mIfaceName, 130 WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler); 131 mWifiMonitor.deregisterHandler(mIfaceName, 132 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler); 133 } 134 } 135 136 @Override 137 public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) { 138 capabilities.max_scan_cache_size = Integer.MAX_VALUE; 139 capabilities.max_scan_buckets = MAX_SCAN_BUCKETS; 140 capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN; 141 capabilities.max_rssi_sample_size = 8; 142 capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY; 143 return true; 144 } 145 146 @Override 147 public ChannelHelper getChannelHelper() { 148 return mChannelHelper; 149 } 150 151 @Override 152 public boolean startSingleScan(WifiNative.ScanSettings settings, 153 WifiNative.ScanEventHandler eventHandler) { 154 if (eventHandler == null || settings == null) { 155 Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings 156 + ",eventHandler=" + eventHandler); 157 return false; 158 } 159 synchronized (mSettingsLock) { 160 if (mLastScanSettings != null) { 161 Log.w(TAG, "A single scan is already running"); 162 return false; 163 } 164 165 ChannelCollection allFreqs = mChannelHelper.createChannelCollection(); 166 boolean reportFullResults = false; 167 168 for (int i = 0; i < settings.num_buckets; ++i) { 169 WifiNative.BucketSettings bucketSettings = settings.buckets[i]; 170 if ((bucketSettings.report_events 171 & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { 172 reportFullResults = true; 173 } 174 allFreqs.addChannels(bucketSettings); 175 } 176 177 Set<String> hiddenNetworkSSIDSet = new HashSet<>(); 178 if (settings.hiddenNetworks != null) { 179 int numHiddenNetworks = 180 Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN); 181 for (int i = 0; i < numHiddenNetworks; i++) { 182 hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid); 183 } 184 } 185 mLastScanSettings = new LastScanSettings( 186 mClock.getElapsedSinceBootMillis(), 187 reportFullResults, allFreqs, eventHandler); 188 189 boolean success = false; 190 Set<Integer> freqs; 191 if (!allFreqs.isEmpty()) { 192 freqs = allFreqs.getScanFreqs(); 193 success = mWifiNative.scan( 194 mIfaceName, settings.scanType, freqs, hiddenNetworkSSIDSet); 195 if (!success) { 196 Log.e(TAG, "Failed to start scan, freqs=" + freqs); 197 } 198 } else { 199 // There is a scan request but no available channels could be scanned for. 200 // We regard it as a scan failure in this case. 201 Log.e(TAG, "Failed to start scan because there is no available channel to scan"); 202 } 203 if (success) { 204 if (DBG) { 205 Log.d(TAG, "Starting wifi scan for freqs=" + freqs); 206 } 207 208 mScanTimeoutListener = new AlarmManager.OnAlarmListener() { 209 @Override public void onAlarm() { 210 handleScanTimeout(); 211 } 212 }; 213 214 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 215 mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS, 216 TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler); 217 } else { 218 // indicate scan failure async 219 mEventHandler.post(new Runnable() { 220 @Override public void run() { 221 reportScanFailure(); 222 } 223 }); 224 } 225 226 return true; 227 } 228 } 229 230 @Override 231 public WifiScanner.ScanData getLatestSingleScanResults() { 232 return mLatestSingleScanResult; 233 } 234 235 @Override 236 public boolean startBatchedScan(WifiNative.ScanSettings settings, 237 WifiNative.ScanEventHandler eventHandler) { 238 Log.w(TAG, "startBatchedScan() is not supported"); 239 return false; 240 } 241 242 @Override 243 public void stopBatchedScan() { 244 Log.w(TAG, "stopBatchedScan() is not supported"); 245 } 246 247 @Override 248 public void pauseBatchedScan() { 249 Log.w(TAG, "pauseBatchedScan() is not supported"); 250 } 251 252 @Override 253 public void restartBatchedScan() { 254 Log.w(TAG, "restartBatchedScan() is not supported"); 255 } 256 257 private void handleScanTimeout() { 258 synchronized (mSettingsLock) { 259 Log.e(TAG, "Timed out waiting for scan result from wificond"); 260 reportScanFailure(); 261 mScanTimeoutListener = null; 262 } 263 } 264 265 @Override 266 public boolean handleMessage(Message msg) { 267 switch(msg.what) { 268 case WifiMonitor.SCAN_FAILED_EVENT: 269 Log.w(TAG, "Scan failed"); 270 cancelScanTimeout(); 271 reportScanFailure(); 272 break; 273 case WifiMonitor.PNO_SCAN_RESULTS_EVENT: 274 pollLatestScanDataForPno(); 275 break; 276 case WifiMonitor.SCAN_RESULTS_EVENT: 277 cancelScanTimeout(); 278 pollLatestScanData(); 279 break; 280 default: 281 // ignore unknown event 282 } 283 return true; 284 } 285 286 private void cancelScanTimeout() { 287 synchronized (mSettingsLock) { 288 if (mScanTimeoutListener != null) { 289 mAlarmManager.cancel(mScanTimeoutListener); 290 mScanTimeoutListener = null; 291 } 292 } 293 } 294 295 private void reportScanFailure() { 296 synchronized (mSettingsLock) { 297 if (mLastScanSettings != null) { 298 if (mLastScanSettings.singleScanEventHandler != null) { 299 mLastScanSettings.singleScanEventHandler 300 .onScanStatus(WifiNative.WIFI_SCAN_FAILED); 301 } 302 mLastScanSettings = null; 303 } 304 } 305 } 306 307 private void reportPnoScanFailure() { 308 synchronized (mSettingsLock) { 309 if (mLastPnoScanSettings != null) { 310 if (mLastPnoScanSettings.pnoScanEventHandler != null) { 311 mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed(); 312 } 313 // Clean up PNO state, we don't want to continue PNO scanning. 314 mLastPnoScanSettings = null; 315 } 316 } 317 } 318 319 private void pollLatestScanDataForPno() { 320 synchronized (mSettingsLock) { 321 if (mLastPnoScanSettings == null) { 322 // got a scan before we started scanning or after scan was canceled 323 return; 324 } 325 mNativePnoScanResults = mWifiNative.getPnoScanResults(mIfaceName); 326 List<ScanResult> hwPnoScanResults = new ArrayList<>(); 327 int numFilteredScanResults = 0; 328 for (int i = 0; i < mNativePnoScanResults.size(); ++i) { 329 ScanResult result = mNativePnoScanResults.get(i).getScanResult(); 330 long timestamp_ms = result.timestamp / 1000; // convert us -> ms 331 if (timestamp_ms > mLastPnoScanSettings.startTime) { 332 hwPnoScanResults.add(result); 333 } else { 334 numFilteredScanResults++; 335 } 336 } 337 338 if (numFilteredScanResults != 0) { 339 Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results."); 340 } 341 342 if (mLastPnoScanSettings.pnoScanEventHandler != null) { 343 ScanResult[] pnoScanResultsArray = 344 hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]); 345 mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray); 346 } 347 } 348 } 349 350 /** 351 * Check if the provided channel collection contains all the channels. 352 */ 353 private static boolean isAllChannelsScanned(ChannelCollection channelCollection) { 354 // TODO(b/62253332): Get rid of this hack. 355 // We're treating 2g + 5g and 2g + 5g + dfs as all channels scanned to work around 356 // the lack of a proper cache. 357 return (channelCollection.containsBand(WifiScanner.WIFI_BAND_24_GHZ) 358 && channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ)); 359 } 360 361 private void pollLatestScanData() { 362 synchronized (mSettingsLock) { 363 if (mLastScanSettings == null) { 364 // got a scan before we started scanning or after scan was canceled 365 return; 366 } 367 368 mNativeScanResults = mWifiNative.getScanResults(mIfaceName); 369 List<ScanResult> singleScanResults = new ArrayList<>(); 370 int numFilteredScanResults = 0; 371 for (int i = 0; i < mNativeScanResults.size(); ++i) { 372 ScanResult result = mNativeScanResults.get(i).getScanResult(); 373 long timestamp_ms = result.timestamp / 1000; // convert us -> ms 374 if (timestamp_ms > mLastScanSettings.startTime) { 375 if (mLastScanSettings.singleScanFreqs.containsChannel( 376 result.frequency)) { 377 singleScanResults.add(result); 378 } 379 } else { 380 numFilteredScanResults++; 381 } 382 } 383 if (numFilteredScanResults != 0) { 384 Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results."); 385 } 386 387 if (mLastScanSettings.singleScanEventHandler != null) { 388 if (mLastScanSettings.reportSingleScanFullResults) { 389 for (ScanResult scanResult : singleScanResults) { 390 // ignore buckets scanned since there is only one bucket for a single scan 391 mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult, 392 /* bucketsScanned */ 0); 393 } 394 } 395 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR); 396 mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0, 397 isAllChannelsScanned(mLastScanSettings.singleScanFreqs), 398 singleScanResults.toArray(new ScanResult[singleScanResults.size()])); 399 mLastScanSettings.singleScanEventHandler 400 .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE); 401 } 402 403 mLastScanSettings = null; 404 } 405 } 406 407 408 @Override 409 public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) { 410 return null; 411 } 412 413 private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) { 414 return mWifiNative.startPnoScan(mIfaceName, pnoSettings); 415 } 416 417 private void stopHwPnoScan() { 418 mWifiNative.stopPnoScan(mIfaceName); 419 } 420 421 /** 422 * Hw Pno Scan is required only for disconnected PNO when the device supports it. 423 * @param isConnectedPno Whether this is connected PNO vs disconnected PNO. 424 * @return true if HW PNO scan is required, false otherwise. 425 */ 426 private boolean isHwPnoScanRequired(boolean isConnectedPno) { 427 return (!isConnectedPno && mHwPnoScanSupported); 428 } 429 430 @Override 431 public boolean setHwPnoList(WifiNative.PnoSettings settings, 432 WifiNative.PnoEventHandler eventHandler) { 433 synchronized (mSettingsLock) { 434 if (mLastPnoScanSettings != null) { 435 Log.w(TAG, "Already running a PNO scan"); 436 return false; 437 } 438 if (!isHwPnoScanRequired(settings.isConnected)) { 439 return false; 440 } 441 442 if (startHwPnoScan(settings)) { 443 mLastPnoScanSettings = new LastPnoScanSettings( 444 mClock.getElapsedSinceBootMillis(), 445 settings.networkList, eventHandler); 446 447 } else { 448 Log.e(TAG, "Failed to start PNO scan"); 449 reportPnoScanFailure(); 450 } 451 return true; 452 } 453 } 454 455 @Override 456 public boolean resetHwPnoList() { 457 synchronized (mSettingsLock) { 458 if (mLastPnoScanSettings == null) { 459 Log.w(TAG, "No PNO scan running"); 460 return false; 461 } 462 mLastPnoScanSettings = null; 463 // For wificond based PNO, we stop the scan immediately when we reset pno list. 464 stopHwPnoScan(); 465 return true; 466 } 467 } 468 469 @Override 470 public boolean isHwPnoSupported(boolean isConnectedPno) { 471 // Hw Pno Scan is supported only for disconnected PNO when the device supports it. 472 return isHwPnoScanRequired(isConnectedPno); 473 } 474 475 @Override 476 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 477 synchronized (mSettingsLock) { 478 long nowMs = mClock.getElapsedSinceBootMillis(); 479 pw.println("Latest native scan results:"); 480 if (mNativeScanResults != null) { 481 List<ScanResult> scanResults = mNativeScanResults.stream().map(r -> { 482 return r.getScanResult(); 483 }).collect(Collectors.toList()); 484 ScanResultUtil.dumpScanResults(pw, scanResults, nowMs); 485 } 486 pw.println("Latest native pno scan results:"); 487 if (mNativePnoScanResults != null) { 488 List<ScanResult> pnoScanResults = mNativePnoScanResults.stream().map(r -> { 489 return r.getScanResult(); 490 }).collect(Collectors.toList()); 491 ScanResultUtil.dumpScanResults(pw, pnoScanResults, nowMs); 492 } 493 } 494 } 495 496 private static class LastScanSettings { 497 LastScanSettings(long startTime, 498 boolean reportSingleScanFullResults, 499 ChannelCollection singleScanFreqs, 500 WifiNative.ScanEventHandler singleScanEventHandler) { 501 this.startTime = startTime; 502 this.reportSingleScanFullResults = reportSingleScanFullResults; 503 this.singleScanFreqs = singleScanFreqs; 504 this.singleScanEventHandler = singleScanEventHandler; 505 } 506 507 public long startTime; 508 public boolean reportSingleScanFullResults; 509 public ChannelCollection singleScanFreqs; 510 public WifiNative.ScanEventHandler singleScanEventHandler; 511 512 } 513 514 private static class LastPnoScanSettings { 515 LastPnoScanSettings(long startTime, 516 WifiNative.PnoNetwork[] pnoNetworkList, 517 WifiNative.PnoEventHandler pnoScanEventHandler) { 518 this.startTime = startTime; 519 this.pnoNetworkList = pnoNetworkList; 520 this.pnoScanEventHandler = pnoScanEventHandler; 521 } 522 523 public long startTime; 524 public WifiNative.PnoNetwork[] pnoNetworkList; 525 public WifiNative.PnoEventHandler pnoScanEventHandler; 526 527 } 528 529} 530