• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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