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