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