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 android.annotation.NonNull; 20 import android.app.ActivityManager; 21 import android.app.AppOpsManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.WifiManager; 26 import android.net.wifi.WifiScanner; 27 import android.os.Binder; 28 import android.os.UserHandle; 29 import android.os.WorkSource; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 import android.util.Pair; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.server.wifi.util.WifiPermissionsUtil; 36 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Iterator; 40 import java.util.LinkedList; 41 import java.util.List; 42 43 import javax.annotation.concurrent.NotThreadSafe; 44 45 /** 46 * This class manages all scan requests originating from external apps using the 47 * {@link WifiManager#startScan()}. 48 * 49 * This class is responsible for: 50 * a) Forwarding scan requests from {@link WifiManager#startScan()} to 51 * {@link WifiScanner#startScan(WifiScanner.ScanSettings, WifiScanner.ScanListener)}. 52 * Will essentially proxy scan requests from WifiService to WifiScanningService. 53 * b) Cache the results of these scan requests and return them when 54 * {@link WifiManager#getScanResults()} is invoked. 55 * c) Will send out the {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast when new 56 * scan results are available. 57 * d) Throttle scan requests from non-setting apps: 58 * a) Each foreground app can request a max of 59 * {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} scan every 60 * {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS}. 61 * b) Background apps combined can request 1 scan every 62 * {@link #SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS}. 63 * Note: This class is not thread-safe. It needs to be invoked from WifiStateMachine thread only. 64 */ 65 @NotThreadSafe 66 public class ScanRequestProxy { 67 private static final String TAG = "WifiScanRequestProxy"; 68 69 @VisibleForTesting 70 public static final int SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS = 120 * 1000; 71 @VisibleForTesting 72 public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4; 73 @VisibleForTesting 74 public static final int SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS = 30 * 60 * 1000; 75 76 private final Context mContext; 77 private final AppOpsManager mAppOps; 78 private final ActivityManager mActivityManager; 79 private final WifiInjector mWifiInjector; 80 private final WifiConfigManager mWifiConfigManager; 81 private final WifiPermissionsUtil mWifiPermissionsUtil; 82 private final WifiMetrics mWifiMetrics; 83 private final Clock mClock; 84 private WifiScanner mWifiScanner; 85 86 // Verbose logging flag. 87 private boolean mVerboseLoggingEnabled = false; 88 // Flag to decide if we need to scan for hidden networks or not. 89 private boolean mScanningForHiddenNetworksEnabled = false; 90 // Flag to indicate that we're waiting for scan results from an existing request. 91 private boolean mIsScanProcessingComplete = true; 92 // Timestamps for the last scan requested by any background app. 93 private long mLastScanTimestampForBgApps = 0; 94 // Timestamps for the list of last few scan requests by each foreground app. 95 // Keys in the map = Pair<Uid, PackageName> of the app. 96 // Values in the map = List of the last few scan request timestamps from the app. 97 private final ArrayMap<Pair<Integer, String>, LinkedList<Long>> mLastScanTimestampsForFgApps = 98 new ArrayMap(); 99 // Scan results cached from the last full single scan request. 100 private final List<ScanResult> mLastScanResults = new ArrayList<>(); 101 // Common scan listener for scan requests. 102 private class ScanRequestProxyScanListener implements WifiScanner.ScanListener { 103 @Override 104 public void onSuccess() { 105 // Scan request succeeded, wait for results to report to external clients. 106 if (mVerboseLoggingEnabled) { 107 Log.d(TAG, "Scan request succeeded"); 108 } 109 } 110 111 @Override 112 public void onFailure(int reason, String description) { 113 Log.e(TAG, "Scan failure received. reason: " + reason + ",description: " + description); 114 sendScanResultBroadcastIfScanProcessingNotComplete(false); 115 } 116 117 @Override 118 public void onResults(WifiScanner.ScanData[] scanDatas) { 119 if (mVerboseLoggingEnabled) { 120 Log.d(TAG, "Scan results received"); 121 } 122 // For single scans, the array size should always be 1. 123 if (scanDatas.length != 1) { 124 Log.wtf(TAG, "Found more than 1 batch of scan results, Failing..."); 125 sendScanResultBroadcastIfScanProcessingNotComplete(false); 126 return; 127 } 128 WifiScanner.ScanData scanData = scanDatas[0]; 129 ScanResult[] scanResults = scanData.getResults(); 130 if (mVerboseLoggingEnabled) { 131 Log.d(TAG, "Received " + scanResults.length + " scan results"); 132 } 133 // Store the last scan results & send out the scan completion broadcast. 134 mLastScanResults.clear(); 135 mLastScanResults.addAll(Arrays.asList(scanResults)); 136 sendScanResultBroadcastIfScanProcessingNotComplete(true); 137 } 138 139 @Override 140 public void onFullResult(ScanResult fullScanResult) { 141 // Ignore for single scans. 142 } 143 144 @Override 145 public void onPeriodChanged(int periodInMs) { 146 // Ignore for single scans. 147 } 148 }; 149 150 ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, 151 WifiInjector wifiInjector, WifiConfigManager configManager, 152 WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock) { 153 mContext = context; 154 mAppOps = appOpsManager; 155 mActivityManager = activityManager; 156 mWifiInjector = wifiInjector; 157 mWifiConfigManager = configManager; 158 mWifiPermissionsUtil = wifiPermissionUtil; 159 mWifiMetrics = wifiMetrics; 160 mClock = clock; 161 } 162 163 /** 164 * Enable verbose logging. 165 */ 166 public void enableVerboseLogging(int verbose) { 167 mVerboseLoggingEnabled = (verbose > 0); 168 } 169 170 /** 171 * Enable/disable scanning for hidden networks. 172 * @param enable true to enable, false to disable. 173 */ 174 public void enableScanningForHiddenNetworks(boolean enable) { 175 if (mVerboseLoggingEnabled) { 176 Log.d(TAG, "Scanning for hidden networks is " + (enable ? "enabled" : "disabled")); 177 } 178 mScanningForHiddenNetworksEnabled = enable; 179 } 180 181 /** 182 * Helper method to populate WifiScanner handle. This is done lazily because 183 * WifiScanningService is started after WifiService. 184 */ 185 private boolean retrieveWifiScannerIfNecessary() { 186 if (mWifiScanner == null) { 187 mWifiScanner = mWifiInjector.getWifiScanner(); 188 } 189 return mWifiScanner != null; 190 } 191 192 /** 193 * Helper method to send the scan request status broadcast, if there is a scan ongoing. 194 */ 195 private void sendScanResultBroadcastIfScanProcessingNotComplete(boolean scanSucceeded) { 196 if (mIsScanProcessingComplete) { 197 Log.i(TAG, "No ongoing scan request. Don't send scan broadcast."); 198 return; 199 } 200 sendScanResultBroadcast(scanSucceeded); 201 mIsScanProcessingComplete = true; 202 } 203 204 /** 205 * Helper method to send the scan request status broadcast. 206 */ 207 private void sendScanResultBroadcast(boolean scanSucceeded) { 208 // clear calling identity to send broadcast 209 long callingIdentity = Binder.clearCallingIdentity(); 210 try { 211 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 212 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 213 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); 214 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 215 } finally { 216 // restore calling identity 217 Binder.restoreCallingIdentity(callingIdentity); 218 } 219 } 220 221 /** 222 * Helper method to send the scan request failure broadcast to specified package. 223 */ 224 private void sendScanResultFailureBroadcastToPackage(String packageName) { 225 // clear calling identity to send broadcast 226 long callingIdentity = Binder.clearCallingIdentity(); 227 try { 228 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 229 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 230 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); 231 intent.setPackage(packageName); 232 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 233 } finally { 234 // restore calling identity 235 Binder.restoreCallingIdentity(callingIdentity); 236 } 237 } 238 239 private void trimPastScanRequestTimesForForegroundApp( 240 List<Long> scanRequestTimestamps, long currentTimeMillis) { 241 Iterator<Long> timestampsIter = scanRequestTimestamps.iterator(); 242 while (timestampsIter.hasNext()) { 243 Long scanRequestTimeMillis = timestampsIter.next(); 244 if ((currentTimeMillis - scanRequestTimeMillis) 245 > SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS) { 246 timestampsIter.remove(); 247 } else { 248 // This list is sorted by timestamps, so we can skip any more checks 249 break; 250 } 251 } 252 } 253 254 private LinkedList<Long> getOrCreateScanRequestTimestampsForForegroundApp( 255 int callingUid, String packageName) { 256 Pair<Integer, String> uidAndPackageNamePair = Pair.create(callingUid, packageName); 257 LinkedList<Long> scanRequestTimestamps = 258 mLastScanTimestampsForFgApps.get(uidAndPackageNamePair); 259 if (scanRequestTimestamps == null) { 260 scanRequestTimestamps = new LinkedList<>(); 261 mLastScanTimestampsForFgApps.put(uidAndPackageNamePair, scanRequestTimestamps); 262 } 263 return scanRequestTimestamps; 264 } 265 266 /** 267 * Checks if the scan request from the app (specified by packageName) needs 268 * to be throttled. 269 * The throttle limit allows a max of {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} 270 * in {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS} window. 271 */ 272 private boolean shouldScanRequestBeThrottledForForegroundApp( 273 int callingUid, String packageName) { 274 LinkedList<Long> scanRequestTimestamps = 275 getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName); 276 long currentTimeMillis = mClock.getElapsedSinceBootMillis(); 277 // First evict old entries from the list. 278 trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis); 279 if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) { 280 return true; 281 } 282 // Proceed with the scan request and record the time. 283 scanRequestTimestamps.addLast(currentTimeMillis); 284 return false; 285 } 286 287 /** 288 * Checks if the scan request from a background app needs to be throttled. 289 */ 290 private boolean shouldScanRequestBeThrottledForBackgroundApp() { 291 long lastScanMs = mLastScanTimestampForBgApps; 292 long elapsedRealtime = mClock.getElapsedSinceBootMillis(); 293 if (lastScanMs != 0 294 && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) { 295 return true; 296 } 297 // Proceed with the scan request and record the time. 298 mLastScanTimestampForBgApps = elapsedRealtime; 299 return false; 300 } 301 302 /** 303 * Check if the request comes from background app. 304 */ 305 private boolean isRequestFromBackground(int callingUid, String packageName) { 306 mAppOps.checkPackage(callingUid, packageName); 307 // getPackageImportance requires PACKAGE_USAGE_STATS permission, so clearing the incoming 308 // identity so the permission check can be done on system process where wifi runs in. 309 long callingIdentity = Binder.clearCallingIdentity(); 310 // TODO(b/74970282): This try/catch block may not be necessary (here & above) because all 311 // of these calls are already in WSM thread context (offloaded from app's binder thread). 312 try { 313 return mActivityManager.getPackageImportance(packageName) 314 > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 315 } finally { 316 Binder.restoreCallingIdentity(callingIdentity); 317 } 318 } 319 320 /** 321 * Checks if the scan request from the app (specified by callingUid & packageName) needs 322 * to be throttled. 323 */ 324 private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName) { 325 boolean isThrottled; 326 if (isRequestFromBackground(callingUid, packageName)) { 327 isThrottled = shouldScanRequestBeThrottledForBackgroundApp(); 328 if (isThrottled) { 329 if (mVerboseLoggingEnabled) { 330 Log.v(TAG, "Background scan app request [" + callingUid + ", " 331 + packageName + "]"); 332 } 333 mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount(); 334 } 335 } else { 336 isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName); 337 if (isThrottled) { 338 if (mVerboseLoggingEnabled) { 339 Log.v(TAG, "Foreground scan app request [" + callingUid + ", " 340 + packageName + "]"); 341 } 342 mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount(); 343 } 344 } 345 mWifiMetrics.incrementExternalAppOneshotScanRequestsCount(); 346 return isThrottled; 347 } 348 349 /** 350 * Initiate a wifi scan. 351 * 352 * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid. 353 * @return true if the scan request was placed or a scan is already ongoing, false otherwise. 354 */ 355 public boolean startScan(int callingUid, String packageName) { 356 if (!retrieveWifiScannerIfNecessary()) { 357 Log.e(TAG, "Failed to retrieve wifiscanner"); 358 sendScanResultFailureBroadcastToPackage(packageName); 359 return false; 360 } 361 boolean fromSettingsOrSetupWizard = 362 mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid) 363 || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid); 364 // Check and throttle scan request from apps without NETWORK_SETTINGS permission. 365 if (!fromSettingsOrSetupWizard 366 && shouldScanRequestBeThrottledForApp(callingUid, packageName)) { 367 Log.i(TAG, "Scan request from " + packageName + " throttled"); 368 sendScanResultFailureBroadcastToPackage(packageName); 369 return false; 370 } 371 // Create a worksource using the caller's UID. 372 WorkSource workSource = new WorkSource(callingUid); 373 374 // Create the scan settings. 375 WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); 376 // Scan requests from apps with network settings will be of high accuracy type. 377 if (fromSettingsOrSetupWizard) { 378 settings.type = WifiScanner.TYPE_HIGH_ACCURACY; 379 } 380 // always do full scans 381 settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; 382 settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN 383 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; 384 if (mScanningForHiddenNetworksEnabled) { 385 // retrieve the list of hidden network SSIDs to scan for, if enabled. 386 List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList = 387 mWifiConfigManager.retrieveHiddenNetworkList(); 388 settings.hiddenNetworks = hiddenNetworkList.toArray( 389 new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]); 390 } 391 mWifiScanner.startScan(settings, new ScanRequestProxyScanListener(), workSource); 392 mIsScanProcessingComplete = false; 393 return true; 394 } 395 396 /** 397 * Return the results of the most recent access point scan, in the form of 398 * a list of {@link ScanResult} objects. 399 * @return the list of results 400 */ 401 public List<ScanResult> getScanResults() { 402 return mLastScanResults; 403 } 404 405 /** 406 * Clear the stored scan results. 407 */ 408 public void clearScanResults() { 409 mLastScanResults.clear(); 410 mLastScanTimestampForBgApps = 0; 411 mLastScanTimestampsForFgApps.clear(); 412 } 413 414 /** 415 * Clear any scan timestamps being stored for the app. 416 * 417 * @param uid Uid of the package. 418 * @param packageName Name of the package. 419 */ 420 public void clearScanRequestTimestampsForApp(@NonNull String packageName, int uid) { 421 if (mVerboseLoggingEnabled) { 422 Log.v(TAG, "Clearing scan request timestamps for uid=" + uid + ", packageName=" 423 + packageName); 424 } 425 mLastScanTimestampsForFgApps.remove(Pair.create(uid, packageName)); 426 } 427 } 428