1 /* 2 * Copyright (C) 2018 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 com.android.server.wifi; 18 19 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_SCAN_THROTTLE_ENABLED; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.AppOpsManager; 25 import android.app.BroadcastOptions; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.wifi.IScanResultsCallback; 29 import android.net.wifi.ScanResult; 30 import android.net.wifi.WifiManager; 31 import android.net.wifi.WifiScanner; 32 import android.net.wifi.util.ScanResultUtil; 33 import android.os.Bundle; 34 import android.os.RemoteCallbackList; 35 import android.os.RemoteException; 36 import android.os.UserHandle; 37 import android.os.WorkSource; 38 import android.text.TextUtils; 39 import android.util.ArrayMap; 40 import android.util.Log; 41 import android.util.LruCache; 42 import android.util.Pair; 43 44 import androidx.annotation.Keep; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.modules.utils.build.SdkLevel; 49 import com.android.server.wifi.scanner.WifiScannerInternal; 50 import com.android.server.wifi.util.WifiPermissionsUtil; 51 import com.android.wifi.resources.R; 52 53 import java.util.ArrayList; 54 import java.util.HashMap; 55 import java.util.Iterator; 56 import java.util.LinkedList; 57 import java.util.List; 58 import java.util.Map; 59 60 import javax.annotation.concurrent.NotThreadSafe; 61 62 /** 63 * This class manages all scan requests originating from external apps using the 64 * {@link WifiManager#startScan()}. 65 * 66 * This class is responsible for: 67 * a) Enable/Disable scanning based on the request from {@link ActiveModeWarden}. 68 * a) Forwarding scan requests from {@link WifiManager#startScan()} to 69 * {@link WifiScanner#startScan(WifiScanner.ScanSettings, WifiScanner.ScanListener)}. 70 * Will essentially proxy scan requests from WifiService to WifiScanningService. 71 * b) Cache the results of these scan requests and return them when 72 * {@link WifiManager#getScanResults()} is invoked. 73 * c) Will send out the {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast when new 74 * scan results are available. 75 * d) Throttle scan requests from non-setting apps: 76 * a) Each foreground app can request a max of 77 * {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} scan every 78 * {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS}. 79 * b) Background apps combined can request 1 scan every 80 * {@link #SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS}. 81 * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only. 82 */ 83 @NotThreadSafe 84 public class ScanRequestProxy { 85 private static final String TAG = "WifiScanRequestProxy"; 86 87 @VisibleForTesting 88 public static final int SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS = 120 * 1000; 89 @VisibleForTesting 90 public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4; 91 @VisibleForTesting 92 public static final int SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS = 30 * 60 * 1000; 93 94 public static final int PARTIAL_SCAN_CACHE_SIZE = 200; 95 96 private final Context mContext; 97 private final WifiThreadRunner mWifiThreadRunner; 98 private final AppOpsManager mAppOps; 99 private final ActivityManager mActivityManager; 100 private final WifiInjector mWifiInjector; 101 private final WifiConfigManager mWifiConfigManager; 102 private final WifiPermissionsUtil mWifiPermissionsUtil; 103 private final WifiMetrics mWifiMetrics; 104 private final Clock mClock; 105 private final WifiSettingsConfigStore mSettingsConfigStore; 106 private WifiScannerInternal mWifiScanner; 107 108 // Verbose logging flag. 109 private boolean mVerboseLoggingEnabled = false; 110 private final Object mThrottleEnabledLock = new Object(); 111 @GuardedBy("mThrottleEnabledLock") 112 private boolean mThrottleEnabled = true; 113 // Flag to decide if we need to scan or not. 114 private boolean mScanningEnabled = false; 115 // Flag to decide if we need to scan for hidden networks or not. 116 private boolean mScanningForHiddenNetworksEnabled = false; 117 // Timestamps for the last scan requested by any background app. 118 private long mLastScanTimestampForBgApps = 0; 119 // Timestamps for the list of last few scan requests by each foreground app. 120 // Keys in the map = Pair<Uid, PackageName> of the app. 121 // Values in the map = List of the last few scan request timestamps from the app. 122 private final ArrayMap<Pair<Integer, String>, LinkedList<Long>> mLastScanTimestampsForFgApps = 123 new ArrayMap(); 124 // Full scan results cached from the last full single scan request. 125 // Stored as a map of bssid -> ScanResult to allow other clients to perform ScanResult lookup 126 // for bssid more efficiently. 127 private final Map<String, ScanResult> mFullScanCache = new HashMap<>(); 128 // Partial scan results cached since the last full single scan request. 129 private final LruCache<String, ScanResult> mPartialScanCache = 130 new LruCache<>(PARTIAL_SCAN_CACHE_SIZE); 131 // external ScanResultCallback tracker 132 private final RemoteCallbackList<IScanResultsCallback> mRegisteredScanResultsCallbacks; 133 private class GlobalScanListener implements WifiScanner.ScanListener { 134 @Override onSuccess()135 public void onSuccess() { 136 // Ignore. These will be processed from the scan request listener. 137 } 138 139 @Override onFailure(int reason, String description)140 public void onFailure(int reason, String description) { 141 // Ignore. These will be processed from the scan request listener. 142 } 143 144 @Override onResults(WifiScanner.ScanData[] scanDatas)145 public void onResults(WifiScanner.ScanData[] scanDatas) { 146 if (mVerboseLoggingEnabled) { 147 Log.d(TAG, "Scan results received"); 148 } 149 // For single scans, the array size should always be 1. 150 if (scanDatas.length != 1) { 151 Log.wtf(TAG, "Found more than 1 batch of scan results, Failing..."); 152 sendScanResultBroadcast(false); 153 return; 154 } 155 WifiScanner.ScanData scanData = scanDatas[0]; 156 ScanResult[] scanResults = scanData.getResults(); 157 if (mVerboseLoggingEnabled) { 158 Log.d(TAG, "Received " + scanResults.length + " scan results"); 159 } 160 // Only process full band scan results. 161 boolean isFullBandScan = WifiScanner.isFullBandScan( 162 scanData.getScannedBandsInternal(), false); 163 if (isFullBandScan) { 164 // If is full scan, clear the cache so only the latest data is available 165 mFullScanCache.clear(); 166 mPartialScanCache.evictAll(); 167 } 168 for (ScanResult s : scanResults) { 169 ScanResult scanResult = mFullScanCache.get(s.BSSID); 170 if (isFullBandScan && scanResult == null) { 171 mFullScanCache.put(s.BSSID, s); 172 continue; 173 } 174 // If a hidden network is configured, wificond may report two scan results for 175 // the same BSS, ie. One with the SSID and another one without SSID. So avoid 176 // overwriting the scan result of the same BSS with Hidden SSID scan result 177 if (scanResult != null) { 178 if (TextUtils.isEmpty(scanResult.SSID) || !TextUtils.isEmpty(s.SSID)) { 179 mFullScanCache.put(s.BSSID, s); 180 } 181 continue; 182 } 183 scanResult = mPartialScanCache.get(s.BSSID); 184 if (scanResult == null 185 || TextUtils.isEmpty(scanResult.SSID) || !TextUtils.isEmpty(s.SSID)) { 186 mPartialScanCache.put(s.BSSID, s); 187 } 188 } 189 if (isFullBandScan) { 190 // Only trigger broadcasts for full scans 191 sendScanResultBroadcast(true); 192 sendScanResultsAvailableToCallbacks(); 193 } 194 } 195 196 @Override onFullResult(ScanResult fullScanResult)197 public void onFullResult(ScanResult fullScanResult) { 198 // Ignore for single scans. 199 } 200 201 @Override onPeriodChanged(int periodInMs)202 public void onPeriodChanged(int periodInMs) { 203 // Ignore for single scans. 204 } 205 }; 206 207 // Common scan listener for scan requests initiated by this class. 208 private class ScanRequestProxyScanListener implements WifiScanner.ScanListener { 209 @Override onSuccess()210 public void onSuccess() { 211 // Scan request succeeded, wait for results to report to external clients. 212 if (mVerboseLoggingEnabled) { 213 Log.d(TAG, "Scan request succeeded"); 214 } 215 } 216 217 @Override onFailure(int reason, String description)218 public void onFailure(int reason, String description) { 219 Log.e(TAG, "Scan failure received. reason: " + reason + ",description: " + description); 220 sendScanResultBroadcast(false); 221 } 222 223 @Override onResults(WifiScanner.ScanData[] scanDatas)224 public void onResults(WifiScanner.ScanData[] scanDatas) { 225 // Ignore. These will be processed from the global listener. 226 } 227 228 @Override onFullResult(ScanResult fullScanResult)229 public void onFullResult(ScanResult fullScanResult) { 230 // Ignore for single scans. 231 } 232 233 @Override onPeriodChanged(int periodInMs)234 public void onPeriodChanged(int periodInMs) { 235 // Ignore for single scans. 236 } 237 }; 238 ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, WifiInjector wifiInjector, WifiConfigManager configManager, WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock, WifiThreadRunner runner, WifiSettingsConfigStore settingsConfigStore)239 ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, 240 WifiInjector wifiInjector, WifiConfigManager configManager, 241 WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock, 242 WifiThreadRunner runner, WifiSettingsConfigStore settingsConfigStore) { 243 mContext = context; 244 mWifiThreadRunner = runner; 245 mAppOps = appOpsManager; 246 mActivityManager = activityManager; 247 mWifiInjector = wifiInjector; 248 mWifiConfigManager = configManager; 249 mWifiPermissionsUtil = wifiPermissionUtil; 250 mWifiMetrics = wifiMetrics; 251 mClock = clock; 252 mSettingsConfigStore = settingsConfigStore; 253 mRegisteredScanResultsCallbacks = new RemoteCallbackList<>(); 254 } 255 256 /** 257 * Enable verbose logging. 258 */ enableVerboseLogging(boolean verboseEnabled)259 public void enableVerboseLogging(boolean verboseEnabled) { 260 mVerboseLoggingEnabled = verboseEnabled; 261 } 262 updateThrottleEnabled()263 private void updateThrottleEnabled() { 264 synchronized (mThrottleEnabledLock) { 265 // Start listening for throttle settings change after we retrieve scanner instance. 266 mThrottleEnabled = mSettingsConfigStore.get(WIFI_SCAN_THROTTLE_ENABLED); 267 if (mVerboseLoggingEnabled) { 268 Log.v(TAG, "Scan throttle enabled " + mThrottleEnabled); 269 } 270 } 271 } 272 273 /** 274 * Helper method to populate WifiScanner handle. This is done lazily because 275 * WifiScanningService is started after WifiService. 276 */ retrieveWifiScannerIfNecessary()277 private boolean retrieveWifiScannerIfNecessary() { 278 if (mWifiScanner == null) { 279 mWifiScanner = WifiLocalServices.getService(WifiScannerInternal.class); 280 updateThrottleEnabled(); 281 // Register the global scan listener. 282 if (mWifiScanner != null) { 283 mWifiScanner.registerScanListener( 284 new WifiScannerInternal.ScanListener(new GlobalScanListener(), 285 mWifiThreadRunner)); 286 } 287 } 288 return mWifiScanner != null; 289 } 290 291 /** 292 * Method that lets public apps know that scans are available. 293 * 294 * @param context Context to use for the notification 295 * @param available boolean indicating if scanning is available 296 */ sendScanAvailableBroadcast(Context context, boolean available)297 private void sendScanAvailableBroadcast(Context context, boolean available) { 298 Log.d(TAG, "Sending scan available broadcast: " + available); 299 final Intent intent = new Intent(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED); 300 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 301 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, available); 302 context.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 303 } 304 enableScanningInternal(boolean enable)305 private void enableScanningInternal(boolean enable) { 306 if (!retrieveWifiScannerIfNecessary()) { 307 Log.e(TAG, "Failed to retrieve wifiscanner"); 308 return; 309 } 310 mWifiScanner.setScanningEnabled(enable); 311 sendScanAvailableBroadcast(mContext, enable); 312 if (!enable) clearScanResults(); 313 Log.i(TAG, "Scanning is " + (enable ? "enabled" : "disabled")); 314 } 315 316 /** 317 * Enable/disable scanning. 318 * 319 * @param enable true to enable, false to disable. 320 * @param enableScanningForHiddenNetworks true to enable scanning for hidden networks, 321 * false to disable. 322 */ enableScanning(boolean enable, boolean enableScanningForHiddenNetworks)323 public void enableScanning(boolean enable, boolean enableScanningForHiddenNetworks) { 324 if (enable) { 325 enableScanningInternal(true); 326 mScanningForHiddenNetworksEnabled = enableScanningForHiddenNetworks; 327 Log.i(TAG, "Scanning for hidden networks is " 328 + (enableScanningForHiddenNetworks ? "enabled" : "disabled")); 329 } else { 330 enableScanningInternal(false); 331 } 332 mScanningEnabled = enable; 333 } 334 335 336 /** 337 * Helper method to send the scan request status broadcast. 338 */ sendScanResultBroadcast(boolean scanSucceeded)339 private void sendScanResultBroadcast(boolean scanSucceeded) { 340 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 341 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 342 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); 343 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, 344 createBroadcastOptionsForScanResultsAvailable(scanSucceeded)); 345 } 346 347 /** 348 * Helper method to send the scan request failure broadcast to specified package. 349 */ sendScanResultFailureBroadcastToPackage(String packageName)350 private void sendScanResultFailureBroadcastToPackage(String packageName) { 351 final boolean scanSucceeded = false; 352 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 353 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 354 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); 355 intent.setPackage(packageName); 356 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, 357 createBroadcastOptionsForScanResultsAvailable(scanSucceeded)); 358 } 359 createBroadcastOptionsForScanResultsAvailable(boolean scanSucceeded)360 static Bundle createBroadcastOptionsForScanResultsAvailable(boolean scanSucceeded) { 361 if (!SdkLevel.isAtLeastU()) return null; 362 363 // Delay delivering the broadcast to apps in the Cached state and apply policy such 364 // that when a new SCAN_RESULTS_AVAILABLE broadcast is sent, any older pending 365 // broadcasts with the same 'scanSucceeded' extra value will be discarded. 366 return BroadcastOptions.makeBasic() 367 .setDeliveryGroupMatchingKey(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, 368 String.valueOf(scanSucceeded)) 369 .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) 370 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) 371 .toBundle(); 372 } 373 trimPastScanRequestTimesForForegroundApp( List<Long> scanRequestTimestamps, long currentTimeMillis)374 private void trimPastScanRequestTimesForForegroundApp( 375 List<Long> scanRequestTimestamps, long currentTimeMillis) { 376 Iterator<Long> timestampsIter = scanRequestTimestamps.iterator(); 377 while (timestampsIter.hasNext()) { 378 Long scanRequestTimeMillis = timestampsIter.next(); 379 if ((currentTimeMillis - scanRequestTimeMillis) 380 > SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS) { 381 timestampsIter.remove(); 382 } else { 383 // This list is sorted by timestamps, so we can skip any more checks 384 break; 385 } 386 } 387 } 388 getOrCreateScanRequestTimestampsForForegroundApp( int callingUid, String packageName)389 private LinkedList<Long> getOrCreateScanRequestTimestampsForForegroundApp( 390 int callingUid, String packageName) { 391 Pair<Integer, String> uidAndPackageNamePair = Pair.create(callingUid, packageName); 392 synchronized (mThrottleEnabledLock) { 393 LinkedList<Long> scanRequestTimestamps = 394 mLastScanTimestampsForFgApps.get(uidAndPackageNamePair); 395 if (scanRequestTimestamps == null) { 396 scanRequestTimestamps = new LinkedList<>(); 397 mLastScanTimestampsForFgApps.put(uidAndPackageNamePair, scanRequestTimestamps); 398 } 399 return scanRequestTimestamps; 400 } 401 } 402 403 /** 404 * Checks if the scan request from the app (specified by packageName) needs 405 * to be throttled. 406 * The throttle limit allows a max of {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} 407 * in {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS} window. 408 */ shouldScanRequestBeThrottledForForegroundApp( int callingUid, String packageName)409 private boolean shouldScanRequestBeThrottledForForegroundApp( 410 int callingUid, String packageName) { 411 if (isPackageNameInExceptionList(packageName, true)) { 412 return false; 413 } 414 LinkedList<Long> scanRequestTimestamps = 415 getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName); 416 long currentTimeMillis = mClock.getElapsedSinceBootMillis(); 417 // First evict old entries from the list. 418 trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis); 419 if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) { 420 return true; 421 } 422 // Proceed with the scan request and record the time. 423 scanRequestTimestamps.addLast(currentTimeMillis); 424 return false; 425 } 426 isPackageNameInExceptionList(String packageName, boolean isForeground)427 private boolean isPackageNameInExceptionList(String packageName, boolean isForeground) { 428 if (packageName == null) { 429 return false; 430 } 431 String[] exceptionList = mContext.getResources().getStringArray(isForeground 432 ? R.array.config_wifiForegroundScanThrottleExceptionList 433 : R.array.config_wifiBackgroundScanThrottleExceptionList); 434 if (exceptionList == null) { 435 return false; 436 } 437 for (String name : exceptionList) { 438 if (TextUtils.equals(packageName, name)) { 439 return true; 440 } 441 } 442 return false; 443 } 444 445 /** 446 * Checks if the scan request from a background app needs to be throttled. 447 */ shouldScanRequestBeThrottledForBackgroundApp(String packageName)448 private boolean shouldScanRequestBeThrottledForBackgroundApp(String packageName) { 449 if (isPackageNameInExceptionList(packageName, false)) { 450 return false; 451 } 452 synchronized (mThrottleEnabledLock) { 453 long lastScanMs = mLastScanTimestampForBgApps; 454 long elapsedRealtime = mClock.getElapsedSinceBootMillis(); 455 if (lastScanMs != 0 456 && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) { 457 return true; 458 } 459 // Proceed with the scan request and record the time. 460 mLastScanTimestampForBgApps = elapsedRealtime; 461 return false; 462 } 463 } 464 465 /** 466 * Safely retrieve package importance. 467 */ getPackageImportance(int callingUid, String packageName)468 private int getPackageImportance(int callingUid, String packageName) { 469 try { 470 mAppOps.checkPackage(callingUid, packageName); 471 return mActivityManager.getPackageImportance(packageName); 472 } catch (SecurityException e) { 473 Log.e(TAG, "Failed to check the app state", e); 474 return ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; 475 } 476 } 477 478 /** 479 * Checks if the scan request from the app (specified by callingUid & packageName) needs 480 * to be throttled. 481 */ shouldScanRequestBeThrottledForApp(int callingUid, String packageName, int packageImportance)482 private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName, 483 int packageImportance) { 484 boolean isThrottled; 485 if (packageImportance 486 > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { 487 isThrottled = shouldScanRequestBeThrottledForBackgroundApp(packageName); 488 if (isThrottled) { 489 if (mVerboseLoggingEnabled) { 490 Log.v(TAG, "Background scan app request [" + callingUid + ", " 491 + packageName + "]"); 492 } 493 mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount(); 494 } 495 } else { 496 isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName); 497 if (isThrottled) { 498 if (mVerboseLoggingEnabled) { 499 Log.v(TAG, "Foreground scan app request [" + callingUid + ", " 500 + packageName + "]"); 501 } 502 mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount(); 503 } 504 } 505 mWifiMetrics.incrementExternalAppOneshotScanRequestsCount(); 506 return isThrottled; 507 } 508 509 /** 510 * Initiate a wifi scan. 511 * 512 * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid. 513 * @return true if the scan request was placed or a scan is already ongoing, false otherwise. 514 */ startScan(int callingUid, String packageName)515 public boolean startScan(int callingUid, String packageName) { 516 if (!mScanningEnabled || !retrieveWifiScannerIfNecessary()) { 517 Log.e(TAG, "Failed to retrieve wifiscanner"); 518 sendScanResultFailureBroadcastToPackage(packageName); 519 return false; 520 } 521 boolean fromSettingsOrSetupWizard = 522 mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid) 523 || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid); 524 // Check and throttle scan request unless, 525 // a) App has either NETWORK_SETTINGS or NETWORK_SETUP_WIZARD permission. 526 // b) Throttling has been disabled by user. 527 int packageImportance = getPackageImportance(callingUid, packageName); 528 if (!fromSettingsOrSetupWizard && isScanThrottleEnabled() 529 && shouldScanRequestBeThrottledForApp(callingUid, packageName, 530 packageImportance)) { 531 Log.i(TAG, "Scan request from " + packageName + " throttled"); 532 sendScanResultFailureBroadcastToPackage(packageName); 533 return false; 534 } 535 // Create a worksource using the caller's UID. 536 WorkSource workSource = new WorkSource(callingUid, packageName); 537 mWifiMetrics.getScanMetrics().setWorkSource(workSource); 538 mWifiMetrics.getScanMetrics().setImportance(packageImportance); 539 540 // Create the scan settings. 541 WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); 542 // Scan requests from apps with network settings will be of high accuracy type. 543 if (fromSettingsOrSetupWizard) { 544 settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; 545 } else { 546 if (SdkLevel.isAtLeastS()) { 547 // since the scan request is from a normal app, do not scan all 6Ghz channels. 548 settings.set6GhzPscOnlyEnabled(true); 549 } 550 } 551 settings.band = WifiScanner.WIFI_BAND_ALL; 552 settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN 553 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; 554 if (mScanningForHiddenNetworksEnabled) { 555 settings.hiddenNetworks.clear(); 556 // retrieve the list of hidden network SSIDs from saved network to scan if enabled. 557 settings.hiddenNetworks.addAll(mWifiConfigManager.retrieveHiddenNetworkList(false)); 558 // retrieve the list of hidden network SSIDs from Network suggestion to scan for. 559 settings.hiddenNetworks.addAll(mWifiInjector.getWifiNetworkSuggestionsManager() 560 .retrieveHiddenNetworkList(false)); 561 } 562 mWifiScanner.startScan(settings, 563 new WifiScannerInternal.ScanListener(new ScanRequestProxyScanListener(), 564 mWifiThreadRunner), 565 workSource); 566 return true; 567 } 568 569 /** 570 * Return the results of the most recent access point scan, in the form of 571 * a list of {@link ScanResult} objects. 572 * @return the list of results 573 */ 574 @Keep getScanResults()575 public List<ScanResult> getScanResults() { 576 // return a copy to prevent external modification 577 return new ArrayList<>(combineScanResultsCache().values()); 578 } 579 580 /** 581 * Return the ScanResult from the most recent access point scan for the provided bssid. 582 * 583 * @param bssid BSSID as string {@link ScanResult#BSSID}. 584 * @return ScanResult for the corresponding bssid if found, null otherwise. 585 */ getScanResult(@ullable String bssid)586 public @Nullable ScanResult getScanResult(@Nullable String bssid) { 587 if (bssid == null) return null; 588 ScanResult scanResult = mFullScanCache.get(bssid); 589 if (scanResult == null) { 590 scanResult = mPartialScanCache.get(bssid); 591 if (scanResult == null) return null; 592 } 593 // return a copy to prevent external modification 594 return new ScanResult(scanResult); 595 } 596 597 598 /** 599 * Clear the stored scan results. 600 */ clearScanResults()601 private void clearScanResults() { 602 synchronized (mThrottleEnabledLock) { 603 mFullScanCache.clear(); 604 mPartialScanCache.evictAll(); 605 mLastScanTimestampForBgApps = 0; 606 mLastScanTimestampsForFgApps.clear(); 607 } 608 } 609 610 /** 611 * Clear any scan timestamps being stored for the app. 612 * 613 * @param uid Uid of the package. 614 * @param packageName Name of the package. 615 */ clearScanRequestTimestampsForApp(@onNull String packageName, int uid)616 public void clearScanRequestTimestampsForApp(@NonNull String packageName, int uid) { 617 synchronized (mThrottleEnabledLock) { 618 if (mVerboseLoggingEnabled) { 619 Log.v(TAG, "Clearing scan request timestamps for uid=" + uid + ", packageName=" 620 + packageName); 621 } 622 mLastScanTimestampsForFgApps.remove(Pair.create(uid, packageName)); 623 } 624 } 625 sendScanResultsAvailableToCallbacks()626 private void sendScanResultsAvailableToCallbacks() { 627 int itemCount = mRegisteredScanResultsCallbacks.beginBroadcast(); 628 for (int i = 0; i < itemCount; i++) { 629 try { 630 mRegisteredScanResultsCallbacks.getBroadcastItem(i).onScanResultsAvailable(); 631 } catch (RemoteException e) { 632 Log.e(TAG, "onScanResultsAvailable: remote exception -- " + e); 633 } 634 } 635 mRegisteredScanResultsCallbacks.finishBroadcast(); 636 } 637 638 /** Combine the full and partial scan results */ combineScanResultsCache()639 private Map<String, ScanResult> combineScanResultsCache() { 640 Map<String, ScanResult> combinedCache = new HashMap<>(); 641 combinedCache.putAll(mFullScanCache); 642 combinedCache.putAll(mPartialScanCache.snapshot()); 643 return combinedCache; 644 } 645 646 /** 647 * Register a callback on scan event 648 * @param callback IScanResultListener instance to add. 649 * @return true if succeed otherwise false. 650 */ registerScanResultsCallback(IScanResultsCallback callback)651 public boolean registerScanResultsCallback(IScanResultsCallback callback) { 652 return mRegisteredScanResultsCallbacks.register(callback); 653 } 654 655 /** 656 * Unregister a callback on scan event 657 * @param callback IScanResultListener instance to add. 658 */ unregisterScanResultsCallback(IScanResultsCallback callback)659 public void unregisterScanResultsCallback(IScanResultsCallback callback) { 660 mRegisteredScanResultsCallbacks.unregister(callback); 661 } 662 663 /** 664 * Enable/disable wifi scan throttling from 3rd party apps. 665 */ setScanThrottleEnabled(boolean enable)666 public void setScanThrottleEnabled(boolean enable) { 667 synchronized (mThrottleEnabledLock) { 668 mThrottleEnabled = enable; 669 mSettingsConfigStore.put(WIFI_SCAN_THROTTLE_ENABLED, enable); 670 if (mVerboseLoggingEnabled) { 671 Log.i(TAG, "Scan throttle enabled " + mThrottleEnabled); 672 } 673 // reset internal counters when enabling/disabling throttling 674 mLastScanTimestampsForFgApps.clear(); 675 mLastScanTimestampForBgApps = 0; 676 } 677 } 678 679 /** 680 * Get the persisted Wi-Fi scan throttle state, set by 681 * {@link #setScanThrottleEnabled(boolean)}. 682 */ isScanThrottleEnabled()683 public boolean isScanThrottleEnabled() { 684 synchronized (mThrottleEnabledLock) { 685 return mThrottleEnabled; 686 } 687 } 688 689 /** Indicate whether there are WPA2 personal only networks. */ isWpa2PersonalOnlyNetworkInRange(String ssid)690 public boolean isWpa2PersonalOnlyNetworkInRange(String ssid) { 691 return combineScanResultsCache().values().stream().anyMatch(r -> 692 TextUtils.equals(ssid, r.getWifiSsid().toString()) 693 && ScanResultUtil.isScanResultForPskOnlyNetwork(r)); 694 } 695 696 /** Indicate whether there are WPA3 only networks. */ isWpa3PersonalOnlyNetworkInRange(String ssid)697 public boolean isWpa3PersonalOnlyNetworkInRange(String ssid) { 698 return combineScanResultsCache().values().stream().anyMatch(r -> 699 TextUtils.equals(ssid, r.getWifiSsid().toString()) 700 && ScanResultUtil.isScanResultForSaeOnlyNetwork(r)); 701 } 702 703 /** Indicate whether there are WPA2/WPA3 transition mode networks. */ isWpa2Wpa3PersonalTransitionNetworkInRange(String ssid)704 public boolean isWpa2Wpa3PersonalTransitionNetworkInRange(String ssid) { 705 return combineScanResultsCache().values().stream().anyMatch(r -> 706 TextUtils.equals(ssid, ScanResultUtil.createQuotedSsid(r.SSID)) 707 && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(r)); 708 } 709 710 /** Indicate whether there are OPEN only networks. */ isOpenOnlyNetworkInRange(String ssid)711 public boolean isOpenOnlyNetworkInRange(String ssid) { 712 return combineScanResultsCache().values().stream().anyMatch(r -> 713 TextUtils.equals(ssid, r.getWifiSsid().toString()) 714 && ScanResultUtil.isScanResultForOpenOnlyNetwork(r)); 715 } 716 717 /** Indicate whether there are OWE only networks. */ isOweOnlyNetworkInRange(String ssid)718 public boolean isOweOnlyNetworkInRange(String ssid) { 719 return combineScanResultsCache().values().stream().anyMatch(r -> 720 TextUtils.equals(ssid, r.getWifiSsid().toString()) 721 && ScanResultUtil.isScanResultForOweOnlyNetwork(r)); 722 } 723 724 /** Indicate whether there are WPA2 Enterprise only networks. */ isWpa2EnterpriseOnlyNetworkInRange(String ssid)725 public boolean isWpa2EnterpriseOnlyNetworkInRange(String ssid) { 726 return combineScanResultsCache().values().stream().anyMatch(r -> 727 TextUtils.equals(ssid, r.getWifiSsid().toString()) 728 && ScanResultUtil.isScanResultForWpa2EnterpriseOnlyNetwork(r)); 729 } 730 731 /** Indicate whether there are WPA3 Enterprise only networks. */ isWpa3EnterpriseOnlyNetworkInRange(String ssid)732 public boolean isWpa3EnterpriseOnlyNetworkInRange(String ssid) { 733 return combineScanResultsCache().values().stream().anyMatch(r -> 734 TextUtils.equals(ssid, r.getWifiSsid().toString()) 735 && ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(r)); 736 } 737 } 738