Home | History | Annotate | Download | only in wifi
      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.net.wifi.ScanResult;
     25 import android.net.wifi.WifiManager;
     26 import android.net.wifi.WifiScanner;
     27 import android.os.Binder;
     28 import android.os.UserHandle;
     29 import android.os.WorkSource;
     30 import android.util.ArrayMap;
     31 import android.util.Log;
     32 import android.util.Pair;
     33 
     34 import com.android.internal.annotations.VisibleForTesting;
     35 import com.android.server.wifi.util.WifiPermissionsUtil;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.Iterator;
     40 import java.util.LinkedList;
     41 import java.util.List;
     42 
     43 import javax.annotation.concurrent.NotThreadSafe;
     44 
     45 /**
     46  * This class manages all scan requests originating from external apps using the
     47  * {@link WifiManager#startScan()}.
     48  *
     49  * This class is responsible for:
     50  * a) Forwarding scan requests from {@link WifiManager#startScan()} to
     51  * {@link WifiScanner#startScan(WifiScanner.ScanSettings, WifiScanner.ScanListener)}.
     52  * Will essentially proxy scan requests from WifiService to WifiScanningService.
     53  * b) Cache the results of these scan requests and return them when
     54  * {@link WifiManager#getScanResults()} is invoked.
     55  * c) Will send out the {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast when new
     56  * scan results are available.
     57  * d) Throttle scan requests from non-setting apps:
     58  *  a) Each foreground app can request a max of
     59  *   {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} scan every
     60  *   {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS}.
     61  *  b) Background apps combined can request 1 scan every
     62  *   {@link #SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS}.
     63  * Note: This class is not thread-safe. It needs to be invoked from WifiStateMachine thread only.
     64  */
     65 @NotThreadSafe
     66 public class ScanRequestProxy {
     67     private static final String TAG = "WifiScanRequestProxy";
     68 
     69     @VisibleForTesting
     70     public static final int SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS = 120 * 1000;
     71     @VisibleForTesting
     72     public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4;
     73     @VisibleForTesting
     74     public static final int SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS = 30 * 60 * 1000;
     75 
     76     private final Context mContext;
     77     private final AppOpsManager mAppOps;
     78     private final ActivityManager mActivityManager;
     79     private final WifiInjector mWifiInjector;
     80     private final WifiConfigManager mWifiConfigManager;
     81     private final WifiPermissionsUtil mWifiPermissionsUtil;
     82     private final WifiMetrics mWifiMetrics;
     83     private final Clock mClock;
     84     private WifiScanner mWifiScanner;
     85 
     86     // Verbose logging flag.
     87     private boolean mVerboseLoggingEnabled = false;
     88     // Flag to decide if we need to scan for hidden networks or not.
     89     private boolean mScanningForHiddenNetworksEnabled = false;
     90     // Flag to indicate that we're waiting for scan results from an existing request.
     91     private boolean mIsScanProcessingComplete = true;
     92     // Timestamps for the last scan requested by any background app.
     93     private long mLastScanTimestampForBgApps = 0;
     94     // Timestamps for the list of last few scan requests by each foreground app.
     95     // Keys in the map = Pair<Uid, PackageName> of the app.
     96     // Values in the map = List of the last few scan request timestamps from the app.
     97     private final ArrayMap<Pair<Integer, String>, LinkedList<Long>> mLastScanTimestampsForFgApps =
     98             new ArrayMap();
     99     // Scan results cached from the last full single scan request.
    100     private final List<ScanResult> mLastScanResults = new ArrayList<>();
    101     // Common scan listener for scan requests.
    102     private class ScanRequestProxyScanListener implements WifiScanner.ScanListener {
    103         @Override
    104         public void onSuccess() {
    105             // Scan request succeeded, wait for results to report to external clients.
    106             if (mVerboseLoggingEnabled) {
    107                 Log.d(TAG, "Scan request succeeded");
    108             }
    109         }
    110 
    111         @Override
    112         public void onFailure(int reason, String description) {
    113             Log.e(TAG, "Scan failure received. reason: " + reason + ",description: " + description);
    114             sendScanResultBroadcastIfScanProcessingNotComplete(false);
    115         }
    116 
    117         @Override
    118         public void onResults(WifiScanner.ScanData[] scanDatas) {
    119             if (mVerboseLoggingEnabled) {
    120                 Log.d(TAG, "Scan results received");
    121             }
    122             // For single scans, the array size should always be 1.
    123             if (scanDatas.length != 1) {
    124                 Log.wtf(TAG, "Found more than 1 batch of scan results, Failing...");
    125                 sendScanResultBroadcastIfScanProcessingNotComplete(false);
    126                 return;
    127             }
    128             WifiScanner.ScanData scanData = scanDatas[0];
    129             ScanResult[] scanResults = scanData.getResults();
    130             if (mVerboseLoggingEnabled) {
    131                 Log.d(TAG, "Received " + scanResults.length + " scan results");
    132             }
    133             // Store the last scan results & send out the scan completion broadcast.
    134             mLastScanResults.clear();
    135             mLastScanResults.addAll(Arrays.asList(scanResults));
    136             sendScanResultBroadcastIfScanProcessingNotComplete(true);
    137         }
    138 
    139         @Override
    140         public void onFullResult(ScanResult fullScanResult) {
    141             // Ignore for single scans.
    142         }
    143 
    144         @Override
    145         public void onPeriodChanged(int periodInMs) {
    146             // Ignore for single scans.
    147         }
    148     };
    149 
    150     ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager,
    151                      WifiInjector wifiInjector, WifiConfigManager configManager,
    152                      WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock) {
    153         mContext = context;
    154         mAppOps = appOpsManager;
    155         mActivityManager = activityManager;
    156         mWifiInjector = wifiInjector;
    157         mWifiConfigManager = configManager;
    158         mWifiPermissionsUtil = wifiPermissionUtil;
    159         mWifiMetrics = wifiMetrics;
    160         mClock = clock;
    161     }
    162 
    163     /**
    164      * Enable verbose logging.
    165      */
    166     public void enableVerboseLogging(int verbose) {
    167         mVerboseLoggingEnabled = (verbose > 0);
    168     }
    169 
    170     /**
    171      * Enable/disable scanning for hidden networks.
    172      * @param enable true to enable, false to disable.
    173      */
    174     public void enableScanningForHiddenNetworks(boolean enable) {
    175         if (mVerboseLoggingEnabled) {
    176             Log.d(TAG, "Scanning for hidden networks is " + (enable ? "enabled" : "disabled"));
    177         }
    178         mScanningForHiddenNetworksEnabled = enable;
    179     }
    180 
    181     /**
    182      * Helper method to populate WifiScanner handle. This is done lazily because
    183      * WifiScanningService is started after WifiService.
    184      */
    185     private boolean retrieveWifiScannerIfNecessary() {
    186         if (mWifiScanner == null) {
    187             mWifiScanner = mWifiInjector.getWifiScanner();
    188         }
    189         return mWifiScanner != null;
    190     }
    191 
    192     /**
    193      * Helper method to send the scan request status broadcast, if there is a scan ongoing.
    194      */
    195     private void sendScanResultBroadcastIfScanProcessingNotComplete(boolean scanSucceeded) {
    196         if (mIsScanProcessingComplete) {
    197             Log.i(TAG, "No ongoing scan request. Don't send scan broadcast.");
    198             return;
    199         }
    200         sendScanResultBroadcast(scanSucceeded);
    201         mIsScanProcessingComplete = true;
    202     }
    203 
    204     /**
    205      * Helper method to send the scan request status broadcast.
    206      */
    207     private void sendScanResultBroadcast(boolean scanSucceeded) {
    208         // clear calling identity to send broadcast
    209         long callingIdentity = Binder.clearCallingIdentity();
    210         try {
    211             Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    212             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    213             intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded);
    214             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    215         } finally {
    216             // restore calling identity
    217             Binder.restoreCallingIdentity(callingIdentity);
    218         }
    219     }
    220 
    221     /**
    222      * Helper method to send the scan request failure broadcast to specified package.
    223      */
    224     private void sendScanResultFailureBroadcastToPackage(String packageName) {
    225         // clear calling identity to send broadcast
    226         long callingIdentity = Binder.clearCallingIdentity();
    227         try {
    228             Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    229             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    230             intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
    231             intent.setPackage(packageName);
    232             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    233         } finally {
    234             // restore calling identity
    235             Binder.restoreCallingIdentity(callingIdentity);
    236         }
    237     }
    238 
    239     private void trimPastScanRequestTimesForForegroundApp(
    240             List<Long> scanRequestTimestamps, long currentTimeMillis) {
    241         Iterator<Long> timestampsIter = scanRequestTimestamps.iterator();
    242         while (timestampsIter.hasNext()) {
    243             Long scanRequestTimeMillis = timestampsIter.next();
    244             if ((currentTimeMillis - scanRequestTimeMillis)
    245                     > SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS) {
    246                 timestampsIter.remove();
    247             } else {
    248                 // This list is sorted by timestamps, so we can skip any more checks
    249                 break;
    250             }
    251         }
    252     }
    253 
    254     private LinkedList<Long> getOrCreateScanRequestTimestampsForForegroundApp(
    255             int callingUid, String packageName) {
    256         Pair<Integer, String> uidAndPackageNamePair = Pair.create(callingUid, packageName);
    257         LinkedList<Long> scanRequestTimestamps =
    258                 mLastScanTimestampsForFgApps.get(uidAndPackageNamePair);
    259         if (scanRequestTimestamps == null) {
    260             scanRequestTimestamps = new LinkedList<>();
    261             mLastScanTimestampsForFgApps.put(uidAndPackageNamePair, scanRequestTimestamps);
    262         }
    263         return scanRequestTimestamps;
    264     }
    265 
    266     /**
    267      * Checks if the scan request from the app (specified by packageName) needs
    268      * to be throttled.
    269      * The throttle limit allows a max of {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS}
    270      * in {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS} window.
    271      */
    272     private boolean shouldScanRequestBeThrottledForForegroundApp(
    273             int callingUid, String packageName) {
    274         LinkedList<Long> scanRequestTimestamps =
    275                 getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName);
    276         long currentTimeMillis = mClock.getElapsedSinceBootMillis();
    277         // First evict old entries from the list.
    278         trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis);
    279         if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) {
    280             return true;
    281         }
    282         // Proceed with the scan request and record the time.
    283         scanRequestTimestamps.addLast(currentTimeMillis);
    284         return false;
    285     }
    286 
    287     /**
    288      * Checks if the scan request from a background app needs to be throttled.
    289      */
    290     private boolean shouldScanRequestBeThrottledForBackgroundApp() {
    291         long lastScanMs = mLastScanTimestampForBgApps;
    292         long elapsedRealtime = mClock.getElapsedSinceBootMillis();
    293         if (lastScanMs != 0
    294                 && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) {
    295             return true;
    296         }
    297         // Proceed with the scan request and record the time.
    298         mLastScanTimestampForBgApps = elapsedRealtime;
    299         return false;
    300     }
    301 
    302     /**
    303      * Check if the request comes from background app.
    304      */
    305     private boolean isRequestFromBackground(int callingUid, String packageName) {
    306         mAppOps.checkPackage(callingUid, packageName);
    307         // getPackageImportance requires PACKAGE_USAGE_STATS permission, so clearing the incoming
    308         // identity so the permission check can be done on system process where wifi runs in.
    309         long callingIdentity = Binder.clearCallingIdentity();
    310         // TODO(b/74970282): This try/catch block may not be necessary (here & above) because all
    311         // of these calls are already in WSM thread context (offloaded from app's binder thread).
    312         try {
    313             return mActivityManager.getPackageImportance(packageName)
    314                     > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
    315         } finally {
    316             Binder.restoreCallingIdentity(callingIdentity);
    317         }
    318     }
    319 
    320     /**
    321      * Checks if the scan request from the app (specified by callingUid & packageName) needs
    322      * to be throttled.
    323      */
    324     private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName) {
    325         boolean isThrottled;
    326         if (isRequestFromBackground(callingUid, packageName)) {
    327             isThrottled = shouldScanRequestBeThrottledForBackgroundApp();
    328             if (isThrottled) {
    329                 if (mVerboseLoggingEnabled) {
    330                     Log.v(TAG, "Background scan app request [" + callingUid + ", "
    331                             + packageName + "]");
    332                 }
    333                 mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount();
    334             }
    335         } else {
    336             isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName);
    337             if (isThrottled) {
    338                 if (mVerboseLoggingEnabled) {
    339                     Log.v(TAG, "Foreground scan app request [" + callingUid + ", "
    340                             + packageName + "]");
    341                 }
    342                 mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount();
    343             }
    344         }
    345         mWifiMetrics.incrementExternalAppOneshotScanRequestsCount();
    346         return isThrottled;
    347     }
    348 
    349     /**
    350      * Initiate a wifi scan.
    351      *
    352      * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid.
    353      * @return true if the scan request was placed or a scan is already ongoing, false otherwise.
    354      */
    355     public boolean startScan(int callingUid, String packageName) {
    356         if (!retrieveWifiScannerIfNecessary()) {
    357             Log.e(TAG, "Failed to retrieve wifiscanner");
    358             sendScanResultFailureBroadcastToPackage(packageName);
    359             return false;
    360         }
    361         boolean fromSettingsOrSetupWizard =
    362                 mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid)
    363                         || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid);
    364         // Check and throttle scan request from apps without NETWORK_SETTINGS permission.
    365         if (!fromSettingsOrSetupWizard
    366                 && shouldScanRequestBeThrottledForApp(callingUid, packageName)) {
    367             Log.i(TAG, "Scan request from " + packageName + " throttled");
    368             sendScanResultFailureBroadcastToPackage(packageName);
    369             return false;
    370         }
    371         // Create a worksource using the caller's UID.
    372         WorkSource workSource = new WorkSource(callingUid);
    373 
    374         // Create the scan settings.
    375         WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
    376         // Scan requests from apps with network settings will be of high accuracy type.
    377         if (fromSettingsOrSetupWizard) {
    378             settings.type = WifiScanner.TYPE_HIGH_ACCURACY;
    379         }
    380         // always do full scans
    381         settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
    382         settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
    383                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
    384         if (mScanningForHiddenNetworksEnabled) {
    385             // retrieve the list of hidden network SSIDs to scan for, if enabled.
    386             List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList =
    387                     mWifiConfigManager.retrieveHiddenNetworkList();
    388             settings.hiddenNetworks = hiddenNetworkList.toArray(
    389                     new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
    390         }
    391         mWifiScanner.startScan(settings, new ScanRequestProxyScanListener(), workSource);
    392         mIsScanProcessingComplete = false;
    393         return true;
    394     }
    395 
    396     /**
    397      * Return the results of the most recent access point scan, in the form of
    398      * a list of {@link ScanResult} objects.
    399      * @return the list of results
    400      */
    401     public List<ScanResult> getScanResults() {
    402         return mLastScanResults;
    403     }
    404 
    405     /**
    406      * Clear the stored scan results.
    407      */
    408     public void clearScanResults() {
    409         mLastScanResults.clear();
    410         mLastScanTimestampForBgApps = 0;
    411         mLastScanTimestampsForFgApps.clear();
    412     }
    413 
    414     /**
    415      * Clear any scan timestamps being stored for the app.
    416      *
    417      * @param uid Uid of the package.
    418      * @param packageName Name of the package.
    419      */
    420     public void clearScanRequestTimestampsForApp(@NonNull String packageName, int uid) {
    421         if (mVerboseLoggingEnabled) {
    422             Log.v(TAG, "Clearing scan request timestamps for uid=" + uid + ", packageName="
    423                     + packageName);
    424         }
    425         mLastScanTimestampsForFgApps.remove(Pair.create(uid, packageName));
    426     }
    427 }
    428