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