diff options
-rw-r--r-- | service/java/com/android/server/wifi/WifiServiceImpl.java | 153 | ||||
-rw-r--r-- | tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java | 96 |
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)); + } } |