summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java153
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java96
2 files changed, 229 insertions, 20 deletions
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index b1569c17d..ed1b49314 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -30,6 +30,8 @@ import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
@@ -74,12 +76,13 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -131,15 +134,26 @@ public class WifiServiceImpl extends IWifiManager.Stub {
private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
+ // Default scan background throttling interval if not overriden in settings
+ private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
+
+ // Apps with importance higher than this value is considered as background app.
+ private static final int BACKGROUND_IMPORTANCE_CUTOFF =
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
final WifiStateMachine mWifiStateMachine;
private final Context mContext;
private final FrameworkFacade mFacade;
+ private final Clock mClock;
+ private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
private final PowerManager mPowerManager;
private final AppOpsManager mAppOps;
private final UserManager mUserManager;
+ private final ActivityManager mActivityManager;
private final WifiCountryCode mCountryCode;
+ private long mBackgroundThrottleInterval;
// Debug counter tracking scan requests sent by WifiManager
private int scanRequestCounter = 0;
@@ -156,9 +170,12 @@ public class WifiServiceImpl extends IWifiManager.Stub {
/* Backup/Restore Module */
private final WifiBackupRestore mWifiBackupRestore;
- private WifiScanner mWifiScanner;
+ // Map of package name of background scan apps and last scan timestamp.
+ private final ArrayMap<String, Long> mLastScanTimestamps;
+ private WifiScanner mWifiScanner;
private WifiLog mLog;
+
/**
* Asynchronous channel to WifiStateMachine
*/
@@ -328,6 +345,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
public WifiServiceImpl(Context context, WifiInjector wifiInjector, AsyncChannel asyncChannel) {
mContext = context;
mWifiInjector = wifiInjector;
+ mClock = wifiInjector.getClock();
mFacade = mWifiInjector.getFrameworkFacade();
mWifiMetrics = mWifiInjector.getWifiMetrics();
@@ -339,6 +357,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
mSettingsStore = mWifiInjector.getWifiSettingsStore();
mPowerManager = mContext.getSystemService(PowerManager.class);
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mCertManager = mWifiInjector.getWifiCertManager();
mWifiLockManager = mWifiInjector.getWifiLockManager();
mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
@@ -354,6 +373,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
mLog = mWifiInjector.makeLog(TAG);
mFrameworkFacade = wifiInjector.getFrameworkFacade();
+ mLastScanTimestamps = new ArrayMap<>();
+ updateBackgroundThrottleInterval();
+ updateBackgroundThrottlingWhitelist();
enableVerboseLoggingInternal(getVerboseLoggingLevel());
}
@@ -387,6 +409,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
(wifiEnabled ? "enabled" : "disabled"));
registerForScanModeChange();
+ registerForBackgroundThrottleChanges();
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
@@ -461,11 +484,25 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*
* @param settings If null, use default parameter, i.e. full scan.
* @param workSource If null, all blame is given to the calling uid.
+ * @param packageName Package name of the app that requests wifi scan.
*/
@Override
- public void startScan(ScanSettings settings, WorkSource workSource) {
+ public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
enforceChangePermission();
+
mLog.trace("startScan uid=%").c(Binder.getCallingUid()).flush();
+ // Check and throttle background apps for wifi scan.
+ if (isRequestFromBackground(packageName)) {
+ long lastScanMs = mLastScanTimestamps.getOrDefault(packageName, 0L);
+ long elapsedRealtime = mClock.getElapsedSinceBootMillis();
+
+ if (lastScanMs != 0 && (elapsedRealtime - lastScanMs) < mBackgroundThrottleInterval) {
+ sendFailedScanBroadcast();
+ return;
+ }
+ // Proceed with the scan request and record the time.
+ mLastScanTimestamps.put(packageName, elapsedRealtime);
+ }
synchronized (this) {
if (mWifiScanner == null) {
mWifiScanner = mWifiInjector.getWifiScanner();
@@ -474,21 +511,11 @@ public class WifiServiceImpl extends IWifiManager.Stub {
// Need to send an immediate scan result broadcast in case the
// caller is waiting for a result ..
- // clear calling identity to send broadcast
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- // TODO: investigate if the logic to cancel scans when idle can move to
- // WifiScanningServiceImpl. This will 1 - clean up WifiServiceImpl and 2 -
- // avoid plumbing an awkward path to report a cancelled/failed scan. This will
- // be sent directly until b/31398592 is fixed.
- Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- } finally {
- // restore calling identity
- Binder.restoreCallingIdentity(callingIdentity);
- }
+ // TODO: investigate if the logic to cancel scans when idle can move to
+ // WifiScanningServiceImpl. This will 1 - clean up WifiServiceImpl and 2 -
+ // avoid plumbing an awkward path to report a cancelled/failed scan. This will
+ // be sent directly until b/31398592 is fixed.
+ sendFailedScanBroadcast();
mScanPending = true;
return;
}
@@ -513,6 +540,45 @@ public class WifiServiceImpl extends IWifiManager.Stub {
settings, workSource);
}
+ // Send a failed scan broadcast to indicate the current scan request failed.
+ private void sendFailedScanBroadcast() {
+ // clear calling identity to send broadcast
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ // restore calling identity
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+
+ }
+
+ // Check if the request comes from background.
+ private boolean isRequestFromBackground(String packageName) {
+ // Requests from system or wifi are not background.
+ if (Binder.getCallingUid() == Process.SYSTEM_UID
+ || Binder.getCallingUid() == Process.WIFI_UID) {
+ return false;
+ }
+ mAppOps.checkPackage(Binder.getCallingUid(), packageName);
+ if (mBackgroundThrottlePackageWhitelist.contains(packageName)) {
+ return false;
+ }
+
+ // getPackageImportance requires PACKAGE_USAGE_STATS permission, so clearing the incoming
+ // identify so the permission check can be done on system process where wifi runs in.
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return mActivityManager.getPackageImportance(packageName)
+ > BACKGROUND_IMPORTANCE_CUTOFF;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
@Override
public String getCurrentNetworkWpsNfcConfigurationToken() {
enforceConnectivityInternalPermission();
@@ -541,7 +607,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
if (doScan) {
// Someone requested a scan while we were idle; do a full scan now.
- startScan(null, null);
+ // The package name doesn't matter as the request comes from System UID.
+ startScan(null, null, "");
}
}
@@ -953,7 +1020,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
// Convert the LinkLayerStats into EnergyActivity
- energyInfo = new WifiActivityEnergyInfo(SystemClock.elapsedRealtime(),
+ energyInfo = new WifiActivityEnergyInfo(mClock.getElapsedSinceBootMillis(),
WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, stats.tx_time,
txTimePerLevel, stats.rx_time, rxIdleTime, energyUsed);
}
@@ -1588,6 +1655,52 @@ public class WifiServiceImpl extends IWifiManager.Stub {
mFrameworkFacade.registerContentObserver(mContext,
Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
false, contentObserver);
+
+ }
+
+ // Monitors settings changes related to background wifi scan throttling.
+ private void registerForBackgroundThrottleChanges() {
+ mFrameworkFacade.registerContentObserver(
+ mContext,
+ Settings.Global.getUriFor(
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS),
+ false,
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateBackgroundThrottleInterval();
+ }
+ }
+ );
+ mFrameworkFacade.registerContentObserver(
+ mContext,
+ Settings.Global.getUriFor(
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+ false,
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateBackgroundThrottlingWhitelist();
+ }
+ }
+ );
+ }
+
+ private void updateBackgroundThrottleInterval() {
+ mBackgroundThrottleInterval = mFrameworkFacade.getLongSetting(
+ mContext,
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS,
+ DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS);
+ }
+
+ private void updateBackgroundThrottlingWhitelist() {
+ String setting = mFrameworkFacade.getStringSetting(
+ mContext,
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ mBackgroundThrottlePackageWhitelist.clear();
+ if (setting != null) {
+ mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+ }
}
private void registerForBroadcasts() {
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 6975432b6..77b64ae14 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -29,10 +29,13 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.IpConfiguration;
+import android.net.wifi.ScanSettings;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Handler;
@@ -43,7 +46,9 @@ import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.UserManager;
+import android.os.WorkSource;
import android.os.test.TestLooper;
+import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.util.AsyncChannel;
@@ -69,7 +74,10 @@ import java.io.StringWriter;
public class WifiServiceImplTest {
private static final String TAG = "WifiServiceImplTest";
+ private static final String SCAN_PACKAGE_NAME = "scanPackage";
+ private static final String WHITE_LIST_SCAN_PACKAGE_NAME = "whiteListScanPackage";
private static final int DEFAULT_VERBOSE_LOGGING = 0;
+ private static final long WIFI_BACKGROUND_SCAN_INTERVAL = 10000;
private static final String ANDROID_SYSTEM_PACKAGE = "android";
private static final String TEST_PACKAGE_NAME = "TestPackage";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
@@ -77,6 +85,7 @@ public class WifiServiceImplTest {
@Mock Context mContext;
@Mock WifiInjector mWifiInjector;
+ @Mock Clock mClock;
WifiServiceImpl mWifiServiceImpl;
@Mock WifiController mWifiController;
@@ -98,6 +107,8 @@ public class WifiServiceImplTest {
@Mock ContentResolver mContentResolver;
@Mock UserManager mUserManager;
@Mock WifiConfiguration mApConfig;
+ @Mock ActivityManager mActivityManager;
+ @Mock AppOpsManager mAppOpsManager;
PowerManager mPowerManager;
private class WifiAsyncChannelTester {
@@ -168,6 +179,17 @@ public class WifiServiceImplTest {
when(mContext.getContentResolver()).thenReturn(mContentResolver);
doNothing().when(mFrameworkFacade).registerContentObserver(eq(mContext), any(),
anyBoolean(), any());
+ when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+ when(mFrameworkFacade.getLongSetting(
+ eq(mContext),
+ eq(Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS),
+ anyLong()))
+ .thenReturn(WIFI_BACKGROUND_SCAN_INTERVAL);
+ when(mFrameworkFacade.getStringSetting(
+ eq(mContext),
+ eq(Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST)))
+ .thenReturn(WHITE_LIST_SCAN_PACKAGE_NAME);
IPowerManager powerManagerService = mock(IPowerManager.class);
mPowerManager = new PowerManager(mContext, powerManagerService, new Handler());
when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
@@ -186,6 +208,7 @@ public class WifiServiceImplTest {
when(mWifiInjector.getWifiTrafficPoller()).thenReturn(wifiTrafficPoller);
when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
when(mWifiInjector.getWifiSettingsStore()).thenReturn(mSettingsStore);
+ when(mWifiInjector.getClock()).thenReturn(mClock);
mWifiServiceImpl = new WifiServiceImpl(mContext, mWifiInjector, mAsyncChannel);
mWifiServiceImpl.setWifiHandlerLogForTest(mLog);
}
@@ -587,4 +610,77 @@ public class WifiServiceImplTest {
eq("WifiService"));
mWifiServiceImpl.stopSoftAp();
}
+
+ /**
+ * Ensure foreground apps can always do wifi scans.
+ */
+ @Test
+ public void testWifiScanStartedForeground() {
+ when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+ mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine).startScan(
+ anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+ }
+
+ /**
+ * Ensure background apps get throttled when the previous scan is too close.
+ */
+ @Test
+ public void testWifiScanBackgroundThrottled() {
+ when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+ long startMs = 1000;
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+ mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine).startScan(
+ anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(
+ startMs + WIFI_BACKGROUND_SCAN_INTERVAL - 1000);
+ mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine, times(1)).startScan(
+ anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+ }
+
+ /**
+ * Ensure background apps can do wifi scan when the throttle interval reached.
+ */
+
+ @Test
+ public void testWifiScanBackgroundNotThrottled() {
+ when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+ long startMs = 1000;
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+ mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine).startScan(
+ anyInt(), eq(0), (ScanSettings) eq(null), any(WorkSource.class));
+
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(
+ startMs + WIFI_BACKGROUND_SCAN_INTERVAL + 1000);
+ mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine).startScan(
+ anyInt(), eq(1), (ScanSettings) eq(null), any(WorkSource.class));
+ }
+
+ /**
+ * Ensure background apps can do wifi scan when the throttle interval reached.
+ */
+ @Test
+ public void testWifiScanBackgroundWhiteListed() {
+ when(mActivityManager.getPackageImportance(WHITE_LIST_SCAN_PACKAGE_NAME)).thenReturn(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+ long startMs = 1000;
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+ mWifiServiceImpl.startScan(null, null, WHITE_LIST_SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine).startScan(
+ anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(
+ startMs + WIFI_BACKGROUND_SCAN_INTERVAL - 1000);
+ mWifiServiceImpl.startScan(null, null, WHITE_LIST_SCAN_PACKAGE_NAME);
+ verify(mWifiStateMachine, times(2)).startScan(
+ anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+ }
}