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.app.ActivityManager; 23 import android.app.AppOpsManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.net.wifi.IScanResultsCallback; 27 import android.net.wifi.ScanResult; 28 import android.net.wifi.WifiManager; 29 import android.net.wifi.WifiScanner; 30 import android.os.Handler; 31 import android.os.HandlerExecutor; 32 import android.os.RemoteCallbackList; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.os.WorkSource; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 import android.util.Pair; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.server.wifi.util.WifiPermissionsUtil; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Iterator; 46 import java.util.LinkedList; 47 import java.util.List; 48 49 import javax.annotation.concurrent.NotThreadSafe; 50 51 /** 52 * This class manages all scan requests originating from external apps using the 53 * {@link WifiManager#startScan()}. 54 * 55 * This class is responsible for: 56 * a) Enable/Disable scanning based on the request from {@link ActiveModeWarden}. 57 * a) Forwarding scan requests from {@link WifiManager#startScan()} to 58 * {@link WifiScanner#startScan(WifiScanner.ScanSettings, WifiScanner.ScanListener)}. 59 * Will essentially proxy scan requests from WifiService to WifiScanningService. 60 * b) Cache the results of these scan requests and return them when 61 * {@link WifiManager#getScanResults()} is invoked. 62 * c) Will send out the {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast when new 63 * scan results are available. 64 * d) Throttle scan requests from non-setting apps: 65 * a) Each foreground app can request a max of 66 * {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} scan every 67 * {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS}. 68 * b) Background apps combined can request 1 scan every 69 * {@link #SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS}. 70 * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only. 71 */ 72 @NotThreadSafe 73 public class ScanRequestProxy { 74 private static final String TAG = "WifiScanRequestProxy"; 75 76 @VisibleForTesting 77 public static final int SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS = 120 * 1000; 78 @VisibleForTesting 79 public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4; 80 @VisibleForTesting 81 public static final int SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS = 30 * 60 * 1000; 82 83 private final Context mContext; 84 private final Handler mHandler; 85 private final AppOpsManager mAppOps; 86 private final ActivityManager mActivityManager; 87 private final WifiInjector mWifiInjector; 88 private final WifiConfigManager mWifiConfigManager; 89 private final WifiPermissionsUtil mWifiPermissionsUtil; 90 private final WifiMetrics mWifiMetrics; 91 private final Clock mClock; 92 private final WifiSettingsConfigStore mSettingsConfigStore; 93 private WifiScanner mWifiScanner; 94 95 // Verbose logging flag. 96 private boolean mVerboseLoggingEnabled = false; 97 private boolean mThrottleEnabled = true; 98 // Flag to decide if we need to scan or not. 99 private boolean mScanningEnabled = false; 100 // Flag to decide if we need to scan for hidden networks or not. 101 private boolean mScanningForHiddenNetworksEnabled = false; 102 // Timestamps for the last scan requested by any background app. 103 private long mLastScanTimestampForBgApps = 0; 104 // Timestamps for the list of last few scan requests by each foreground app. 105 // Keys in the map = Pair<Uid, PackageName> of the app. 106 // Values in the map = List of the last few scan request timestamps from the app. 107 private final ArrayMap<Pair<Integer, String>, LinkedList<Long>> mLastScanTimestampsForFgApps = 108 new ArrayMap(); 109 // Scan results cached from the last full single scan request. 110 private final List<ScanResult> mLastScanResults = new ArrayList<>(); 111 // external ScanResultCallback tracker 112 private final RemoteCallbackList<IScanResultsCallback> mRegisteredScanResultsCallbacks; 113 // Global scan listener for listening to all scan requests. 114 private class GlobalScanListener implements WifiScanner.ScanListener { 115 @Override onSuccess()116 public void onSuccess() { 117 // Ignore. These will be processed from the scan request listener. 118 } 119 120 @Override onFailure(int reason, String description)121 public void onFailure(int reason, String description) { 122 // Ignore. These will be processed from the scan request listener. 123 } 124 125 @Override onResults(WifiScanner.ScanData[] scanDatas)126 public void onResults(WifiScanner.ScanData[] scanDatas) { 127 if (mVerboseLoggingEnabled) { 128 Log.d(TAG, "Scan results received"); 129 } 130 // For single scans, the array size should always be 1. 131 if (scanDatas.length != 1) { 132 Log.wtf(TAG, "Found more than 1 batch of scan results, Failing..."); 133 sendScanResultBroadcast(false); 134 return; 135 } 136 WifiScanner.ScanData scanData = scanDatas[0]; 137 ScanResult[] scanResults = scanData.getResults(); 138 if (mVerboseLoggingEnabled) { 139 Log.d(TAG, "Received " + scanResults.length + " scan results"); 140 } 141 // Only process full band scan results. 142 if (WifiScanner.isFullBandScan(scanData.getBandScanned(), false)) { 143 // Store the last scan results & send out the scan completion broadcast. 144 mLastScanResults.clear(); 145 mLastScanResults.addAll(Arrays.asList(scanResults)); 146 sendScanResultBroadcast(true); 147 sendScanResultsAvailableToCallbacks(); 148 } 149 } 150 151 @Override onFullResult(ScanResult fullScanResult)152 public void onFullResult(ScanResult fullScanResult) { 153 // Ignore for single scans. 154 } 155 156 @Override onPeriodChanged(int periodInMs)157 public void onPeriodChanged(int periodInMs) { 158 // Ignore for single scans. 159 } 160 }; 161 162 // Common scan listener for scan requests initiated by this class. 163 private class ScanRequestProxyScanListener implements WifiScanner.ScanListener { 164 @Override onSuccess()165 public void onSuccess() { 166 // Scan request succeeded, wait for results to report to external clients. 167 if (mVerboseLoggingEnabled) { 168 Log.d(TAG, "Scan request succeeded"); 169 } 170 } 171 172 @Override onFailure(int reason, String description)173 public void onFailure(int reason, String description) { 174 Log.e(TAG, "Scan failure received. reason: " + reason + ",description: " + description); 175 sendScanResultBroadcast(false); 176 } 177 178 @Override onResults(WifiScanner.ScanData[] scanDatas)179 public void onResults(WifiScanner.ScanData[] scanDatas) { 180 // Ignore. These will be processed from the global listener. 181 } 182 183 @Override onFullResult(ScanResult fullScanResult)184 public void onFullResult(ScanResult fullScanResult) { 185 // Ignore for single scans. 186 } 187 188 @Override onPeriodChanged(int periodInMs)189 public void onPeriodChanged(int periodInMs) { 190 // Ignore for single scans. 191 } 192 }; 193 ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, WifiInjector wifiInjector, WifiConfigManager configManager, WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock, Handler handler, WifiSettingsConfigStore settingsConfigStore)194 ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, 195 WifiInjector wifiInjector, WifiConfigManager configManager, 196 WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock, 197 Handler handler, WifiSettingsConfigStore settingsConfigStore) { 198 mContext = context; 199 mHandler = handler; 200 mAppOps = appOpsManager; 201 mActivityManager = activityManager; 202 mWifiInjector = wifiInjector; 203 mWifiConfigManager = configManager; 204 mWifiPermissionsUtil = wifiPermissionUtil; 205 mWifiMetrics = wifiMetrics; 206 mClock = clock; 207 mSettingsConfigStore = settingsConfigStore; 208 mRegisteredScanResultsCallbacks = new RemoteCallbackList<>(); 209 } 210 211 /** 212 * Enable verbose logging. 213 */ enableVerboseLogging(int verbose)214 public void enableVerboseLogging(int verbose) { 215 mVerboseLoggingEnabled = (verbose > 0); 216 } 217 218 /** 219 * Helper method to populate WifiScanner handle. This is done lazily because 220 * WifiScanningService is started after WifiService. 221 */ retrieveWifiScannerIfNecessary()222 private boolean retrieveWifiScannerIfNecessary() { 223 if (mWifiScanner == null) { 224 mWifiScanner = mWifiInjector.getWifiScanner(); 225 // Start listening for throttle settings change after we retrieve scanner instance. 226 mThrottleEnabled = mSettingsConfigStore.get(WIFI_SCAN_THROTTLE_ENABLED); 227 if (mVerboseLoggingEnabled) { 228 Log.v(TAG, "Scan throttle enabled " + mThrottleEnabled); 229 } 230 // Register the global scan listener. 231 if (mWifiScanner != null) { 232 mWifiScanner.registerScanListener( 233 new HandlerExecutor(mHandler), new GlobalScanListener()); 234 } 235 } 236 return mWifiScanner != null; 237 } 238 239 /** 240 * Method that lets public apps know that scans are available. 241 * 242 * @param context Context to use for the notification 243 * @param available boolean indicating if scanning is available 244 */ sendScanAvailableBroadcast(Context context, boolean available)245 private void sendScanAvailableBroadcast(Context context, boolean available) { 246 Log.d(TAG, "Sending scan available broadcast: " + available); 247 final Intent intent = new Intent(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED); 248 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 249 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, available); 250 context.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 251 } 252 enableScanningInternal(boolean enable)253 private void enableScanningInternal(boolean enable) { 254 if (!retrieveWifiScannerIfNecessary()) { 255 Log.e(TAG, "Failed to retrieve wifiscanner"); 256 return; 257 } 258 mWifiScanner.setScanningEnabled(enable); 259 sendScanAvailableBroadcast(mContext, enable); 260 clearScanResults(); 261 Log.i(TAG, "Scanning is " + (enable ? "enabled" : "disabled")); 262 } 263 264 /** 265 * Enable/disable scanning. 266 * 267 * @param enable true to enable, false to disable. 268 * @param enableScanningForHiddenNetworks true to enable scanning for hidden networks, 269 * false to disable. 270 */ enableScanning(boolean enable, boolean enableScanningForHiddenNetworks)271 public void enableScanning(boolean enable, boolean enableScanningForHiddenNetworks) { 272 if (enable) { 273 enableScanningInternal(true); 274 mScanningForHiddenNetworksEnabled = enableScanningForHiddenNetworks; 275 Log.i(TAG, "Scanning for hidden networks is " 276 + (enableScanningForHiddenNetworks ? "enabled" : "disabled")); 277 } else { 278 enableScanningInternal(false); 279 } 280 mScanningEnabled = enable; 281 } 282 283 284 /** 285 * Helper method to send the scan request status broadcast. 286 */ sendScanResultBroadcast(boolean scanSucceeded)287 private void sendScanResultBroadcast(boolean scanSucceeded) { 288 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 289 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 290 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); 291 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 292 } 293 294 /** 295 * Helper method to send the scan request failure broadcast to specified package. 296 */ sendScanResultFailureBroadcastToPackage(String packageName)297 private void sendScanResultFailureBroadcastToPackage(String packageName) { 298 Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 299 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 300 intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); 301 intent.setPackage(packageName); 302 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 303 } 304 trimPastScanRequestTimesForForegroundApp( List<Long> scanRequestTimestamps, long currentTimeMillis)305 private void trimPastScanRequestTimesForForegroundApp( 306 List<Long> scanRequestTimestamps, long currentTimeMillis) { 307 Iterator<Long> timestampsIter = scanRequestTimestamps.iterator(); 308 while (timestampsIter.hasNext()) { 309 Long scanRequestTimeMillis = timestampsIter.next(); 310 if ((currentTimeMillis - scanRequestTimeMillis) 311 > SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS) { 312 timestampsIter.remove(); 313 } else { 314 // This list is sorted by timestamps, so we can skip any more checks 315 break; 316 } 317 } 318 } 319 getOrCreateScanRequestTimestampsForForegroundApp( int callingUid, String packageName)320 private LinkedList<Long> getOrCreateScanRequestTimestampsForForegroundApp( 321 int callingUid, String packageName) { 322 Pair<Integer, String> uidAndPackageNamePair = Pair.create(callingUid, packageName); 323 LinkedList<Long> scanRequestTimestamps = 324 mLastScanTimestampsForFgApps.get(uidAndPackageNamePair); 325 if (scanRequestTimestamps == null) { 326 scanRequestTimestamps = new LinkedList<>(); 327 mLastScanTimestampsForFgApps.put(uidAndPackageNamePair, scanRequestTimestamps); 328 } 329 return scanRequestTimestamps; 330 } 331 332 /** 333 * Checks if the scan request from the app (specified by packageName) needs 334 * to be throttled. 335 * The throttle limit allows a max of {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} 336 * in {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS} window. 337 */ shouldScanRequestBeThrottledForForegroundApp( int callingUid, String packageName)338 private boolean shouldScanRequestBeThrottledForForegroundApp( 339 int callingUid, String packageName) { 340 LinkedList<Long> scanRequestTimestamps = 341 getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName); 342 long currentTimeMillis = mClock.getElapsedSinceBootMillis(); 343 // First evict old entries from the list. 344 trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis); 345 if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) { 346 return true; 347 } 348 // Proceed with the scan request and record the time. 349 scanRequestTimestamps.addLast(currentTimeMillis); 350 return false; 351 } 352 353 /** 354 * Checks if the scan request from a background app needs to be throttled. 355 */ shouldScanRequestBeThrottledForBackgroundApp()356 private boolean shouldScanRequestBeThrottledForBackgroundApp() { 357 long lastScanMs = mLastScanTimestampForBgApps; 358 long elapsedRealtime = mClock.getElapsedSinceBootMillis(); 359 if (lastScanMs != 0 360 && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) { 361 return true; 362 } 363 // Proceed with the scan request and record the time. 364 mLastScanTimestampForBgApps = elapsedRealtime; 365 return false; 366 } 367 368 /** 369 * Check if the request comes from background app. 370 */ isRequestFromBackground(int callingUid, String packageName)371 private boolean isRequestFromBackground(int callingUid, String packageName) { 372 mAppOps.checkPackage(callingUid, packageName); 373 try { 374 return mActivityManager.getPackageImportance(packageName) 375 > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 376 } catch (SecurityException e) { 377 Log.e(TAG, "Failed to check the app state", e); 378 return true; 379 } 380 } 381 382 /** 383 * Checks if the scan request from the app (specified by callingUid & packageName) needs 384 * to be throttled. 385 */ shouldScanRequestBeThrottledForApp(int callingUid, String packageName)386 private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName) { 387 boolean isThrottled; 388 if (isRequestFromBackground(callingUid, packageName)) { 389 isThrottled = shouldScanRequestBeThrottledForBackgroundApp(); 390 if (isThrottled) { 391 if (mVerboseLoggingEnabled) { 392 Log.v(TAG, "Background scan app request [" + callingUid + ", " 393 + packageName + "]"); 394 } 395 mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount(); 396 } 397 } else { 398 isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName); 399 if (isThrottled) { 400 if (mVerboseLoggingEnabled) { 401 Log.v(TAG, "Foreground scan app request [" + callingUid + ", " 402 + packageName + "]"); 403 } 404 mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount(); 405 } 406 } 407 mWifiMetrics.incrementExternalAppOneshotScanRequestsCount(); 408 return isThrottled; 409 } 410 411 /** 412 * Initiate a wifi scan. 413 * 414 * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid. 415 * @return true if the scan request was placed or a scan is already ongoing, false otherwise. 416 */ startScan(int callingUid, String packageName)417 public boolean startScan(int callingUid, String packageName) { 418 if (!mScanningEnabled || !retrieveWifiScannerIfNecessary()) { 419 Log.e(TAG, "Failed to retrieve wifiscanner"); 420 sendScanResultFailureBroadcastToPackage(packageName); 421 return false; 422 } 423 boolean fromSettingsOrSetupWizard = 424 mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid) 425 || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid); 426 // Check and throttle scan request unless, 427 // a) App has either NETWORK_SETTINGS or NETWORK_SETUP_WIZARD permission. 428 // b) Throttling has been disabled by user. 429 if (!fromSettingsOrSetupWizard && mThrottleEnabled 430 && shouldScanRequestBeThrottledForApp(callingUid, packageName)) { 431 Log.i(TAG, "Scan request from " + packageName + " throttled"); 432 sendScanResultFailureBroadcastToPackage(packageName); 433 return false; 434 } 435 // Create a worksource using the caller's UID. 436 WorkSource workSource = new WorkSource(callingUid, packageName); 437 438 // Create the scan settings. 439 WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); 440 // Scan requests from apps with network settings will be of high accuracy type. 441 if (fromSettingsOrSetupWizard) { 442 settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; 443 } 444 // always do full scans 445 settings.band = WifiScanner.WIFI_BAND_ALL; 446 settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN 447 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; 448 if (mScanningForHiddenNetworksEnabled) { 449 settings.hiddenNetworks.clear(); 450 // retrieve the list of hidden network SSIDs from saved network to scan for, if enabled. 451 settings.hiddenNetworks.addAll(mWifiConfigManager.retrieveHiddenNetworkList()); 452 // retrieve the list of hidden network SSIDs from Network suggestion to scan for. 453 settings.hiddenNetworks.addAll( 454 mWifiInjector.getWifiNetworkSuggestionsManager().retrieveHiddenNetworkList()); 455 } 456 mWifiScanner.startScan(settings, new HandlerExecutor(mHandler), 457 new ScanRequestProxyScanListener(), workSource); 458 return true; 459 } 460 461 /** 462 * Return the results of the most recent access point scan, in the form of 463 * a list of {@link ScanResult} objects. 464 * @return the list of results 465 */ getScanResults()466 public List<ScanResult> getScanResults() { 467 return mLastScanResults; 468 } 469 470 /** 471 * Clear the stored scan results. 472 */ clearScanResults()473 private void clearScanResults() { 474 mLastScanResults.clear(); 475 mLastScanTimestampForBgApps = 0; 476 mLastScanTimestampsForFgApps.clear(); 477 } 478 479 /** 480 * Clear any scan timestamps being stored for the app. 481 * 482 * @param uid Uid of the package. 483 * @param packageName Name of the package. 484 */ clearScanRequestTimestampsForApp(@onNull String packageName, int uid)485 public void clearScanRequestTimestampsForApp(@NonNull String packageName, int uid) { 486 if (mVerboseLoggingEnabled) { 487 Log.v(TAG, "Clearing scan request timestamps for uid=" + uid + ", packageName=" 488 + packageName); 489 } 490 mLastScanTimestampsForFgApps.remove(Pair.create(uid, packageName)); 491 } 492 sendScanResultsAvailableToCallbacks()493 private void sendScanResultsAvailableToCallbacks() { 494 int itemCount = mRegisteredScanResultsCallbacks.beginBroadcast(); 495 for (int i = 0; i < itemCount; i++) { 496 try { 497 mRegisteredScanResultsCallbacks.getBroadcastItem(i).onScanResultsAvailable(); 498 } catch (RemoteException e) { 499 Log.e(TAG, "onScanResultsAvailable: remote exception -- " + e); 500 } 501 } 502 mRegisteredScanResultsCallbacks.finishBroadcast(); 503 } 504 505 /** 506 * Register a callback on scan event 507 * @param callback IScanResultListener instance to add. 508 * @return true if succeed otherwise false. 509 */ registerScanResultsCallback(IScanResultsCallback callback)510 public boolean registerScanResultsCallback(IScanResultsCallback callback) { 511 return mRegisteredScanResultsCallbacks.register(callback); 512 } 513 514 /** 515 * Unregister a callback on scan event 516 * @param callback IScanResultListener instance to add. 517 */ unregisterScanResultsCallback(IScanResultsCallback callback)518 public void unregisterScanResultsCallback(IScanResultsCallback callback) { 519 mRegisteredScanResultsCallbacks.unregister(callback); 520 } 521 522 /** 523 * Enable/disable wifi scan throttling from 3rd party apps. 524 */ setScanThrottleEnabled(boolean enable)525 public void setScanThrottleEnabled(boolean enable) { 526 mThrottleEnabled = enable; 527 mSettingsConfigStore.put(WIFI_SCAN_THROTTLE_ENABLED, enable); 528 Log.i(TAG, "Scan throttle enabled " + mThrottleEnabled); 529 } 530 531 /** 532 * Get the persisted Wi-Fi scan throttle state, set by 533 * {@link #setScanThrottleEnabled(boolean)}. 534 */ isScanThrottleEnabled()535 public boolean isScanThrottleEnabled() { 536 return mThrottleEnabled; 537 } 538 } 539