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