[BR06] Support check whether network is blocked by data saver
This change adds a DataSaverStatusTracker, which is a helper class to continuously track data saver status through NPMS public API and intents. ConnectivityManager#isUidNetworkingBlocked would use this cached information along with bpf maps to decide whether networking of an uid is blocked. Test: atest FrameworksNetTests:android.net.connectivity.android.net.BpfNetMapsReaderTest Test: atest ConnectivityCoverageTests:android.net.connectivity.android.net.ConnectivityManagerTest Bug: 297836825 Change-Id: I7e13191759430f3ea1f4dec7facc02f16be7146d
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
package android.net;
|
||||
|
||||
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
|
||||
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
|
||||
import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
|
||||
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
|
||||
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
|
||||
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
|
||||
@@ -213,13 +215,16 @@ public class BpfNetMapsReader {
|
||||
* Return whether the network is blocked by firewall chains for the given uid.
|
||||
*
|
||||
* @param uid The target uid.
|
||||
* @param isNetworkMetered Whether the target network is metered.
|
||||
* @param isDataSaverEnabled Whether the data saver is enabled.
|
||||
*
|
||||
* @return True if the network is blocked. Otherwise, false.
|
||||
* @throws ServiceSpecificException if the read fails.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isUidBlockedByFirewallChains(final int uid) {
|
||||
public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
|
||||
boolean isDataSaverEnabled) {
|
||||
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
|
||||
|
||||
final long uidRuleConfig;
|
||||
@@ -235,6 +240,13 @@ public class BpfNetMapsReader {
|
||||
|
||||
final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
|
||||
final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
|
||||
return blockedByAllowChains || blockedByDenyChains;
|
||||
if (blockedByAllowChains || blockedByDenyChains) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isNetworkMetered) return false;
|
||||
if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
|
||||
if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
|
||||
return isDataSaverEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package android.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
|
||||
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
|
||||
import static android.net.NetworkRequest.Type.LISTEN;
|
||||
@@ -25,6 +27,8 @@ import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
|
||||
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
|
||||
import static android.net.QosCallback.QosCallbackRegistrationException;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.FlaggedApi;
|
||||
import android.annotation.IntDef;
|
||||
@@ -37,12 +41,16 @@ import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod;
|
||||
import android.net.IpSecManager.UdpEncapsulationSocket;
|
||||
import android.net.SocketKeepalive.Callback;
|
||||
@@ -74,6 +82,7 @@ import android.util.Range;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import libcore.net.event.NetworkEventDispatcher;
|
||||
|
||||
@@ -95,6 +104,7 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Class that answers queries about the state of network connectivity. It also
|
||||
@@ -6199,13 +6209,99 @@ public class ConnectivityManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the network is blocked for the given uid.
|
||||
* Helper class to track data saver status.
|
||||
*
|
||||
* The class will fetch current data saver status from {@link NetworkPolicyManager} when
|
||||
* initialized, and listening for status changed intent to cache the latest status.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU) // RECEIVER_NOT_EXPORTED requires T.
|
||||
@VisibleForTesting(visibility = PRIVATE)
|
||||
public static class DataSaverStatusTracker extends BroadcastReceiver {
|
||||
private static final Object sDataSaverStatusTrackerLock = new Object();
|
||||
|
||||
private static volatile DataSaverStatusTracker sInstance;
|
||||
|
||||
/**
|
||||
* Gets a static instance of the class.
|
||||
*
|
||||
* @param context A {@link Context} for initialization. Note that since the data saver
|
||||
* status is global on a device, passing any context is equivalent.
|
||||
* @return The static instance of a {@link DataSaverStatusTracker}.
|
||||
*/
|
||||
public static DataSaverStatusTracker getInstance(@NonNull Context context) {
|
||||
if (sInstance == null) {
|
||||
synchronized (sDataSaverStatusTrackerLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new DataSaverStatusTracker(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private final NetworkPolicyManager mNpm;
|
||||
// The value updates on the caller's binder thread or UI thread.
|
||||
private final AtomicBoolean mIsDataSaverEnabled;
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
public DataSaverStatusTracker(final Context context) {
|
||||
// To avoid leaks, take the application context.
|
||||
final Context appContext;
|
||||
if (context instanceof Application) {
|
||||
appContext = context;
|
||||
} else {
|
||||
appContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
if ((appContext.getApplicationInfo().flags & FLAG_PERSISTENT) == 0
|
||||
&& (appContext.getApplicationInfo().flags & FLAG_SYSTEM) == 0) {
|
||||
throw new IllegalStateException("Unexpected caller: "
|
||||
+ appContext.getApplicationInfo().packageName);
|
||||
}
|
||||
|
||||
mNpm = appContext.getSystemService(NetworkPolicyManager.class);
|
||||
final IntentFilter filter = new IntentFilter(
|
||||
ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
|
||||
// The receiver should not receive broadcasts from other Apps.
|
||||
appContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
mIsDataSaverEnabled = new AtomicBoolean();
|
||||
updateDataSaverEnabled();
|
||||
}
|
||||
|
||||
// Runs on caller's UI thread.
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED)) {
|
||||
updateDataSaverEnabled();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected intent " + intent);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getDataSaverEnabled() {
|
||||
return mIsDataSaverEnabled.get();
|
||||
}
|
||||
|
||||
private void updateDataSaverEnabled() {
|
||||
// Uid doesn't really matter, but use a fixed UID to make things clearer.
|
||||
final int dataSaverForCallerUid = mNpm.getRestrictBackgroundStatus(Process.SYSTEM_UID);
|
||||
mIsDataSaverEnabled.set(dataSaverForCallerUid
|
||||
!= ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the network is blocked for the given uid and metered condition.
|
||||
*
|
||||
* Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF
|
||||
* maps and therefore considerably faster. For use by the NetworkStack process only.
|
||||
*
|
||||
* @param uid The target uid.
|
||||
* @return True if all networking is blocked. Otherwise, false.
|
||||
* @param isNetworkMetered Whether the target network is metered.
|
||||
*
|
||||
* @return True if all networking with the given condition is blocked. Otherwise, false.
|
||||
* @throws IllegalStateException if the map cannot be opened.
|
||||
* @throws ServiceSpecificException if the read fails.
|
||||
* @hide
|
||||
@@ -6219,13 +6315,16 @@ public class ConnectivityManager {
|
||||
// @SystemApi(client = MODULE_LIBRARIES)
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
|
||||
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
|
||||
public boolean isUidNetworkingBlocked(int uid) {
|
||||
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
|
||||
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
|
||||
|
||||
return reader.isUidBlockedByFirewallChains(uid);
|
||||
final boolean isDataSaverEnabled;
|
||||
// TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly.
|
||||
final DataSaverStatusTracker dataSaverStatusTracker =
|
||||
DataSaverStatusTracker.getInstance(mContext);
|
||||
isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
|
||||
|
||||
// TODO: If isNetworkMetered is true, check the data saver switch, penalty box
|
||||
// and happy box rules.
|
||||
return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package android.net
|
||||
|
||||
import android.net.BpfNetMapsConstants.DOZABLE_MATCH
|
||||
import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH
|
||||
import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH
|
||||
import android.net.BpfNetMapsConstants.STANDBY_MATCH
|
||||
import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
|
||||
import android.net.BpfNetMapsUtils.getMatchByFirewallChain
|
||||
@@ -36,6 +38,7 @@ import org.junit.runner.RunWith
|
||||
|
||||
private const val TEST_UID1 = 1234
|
||||
private const val TEST_UID2 = TEST_UID1 + 1
|
||||
private const val TEST_UID3 = TEST_UID2 + 1
|
||||
private const val NO_IIF = 0
|
||||
|
||||
// pre-T devices does not support Bpf.
|
||||
@@ -101,23 +104,26 @@ class BpfNetMapsReaderTest {
|
||||
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig))
|
||||
}
|
||||
|
||||
fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false, dataSaver: Boolean = false) =
|
||||
bpfNetMapsReader.isUidNetworkingBlocked(uid, metered, dataSaver)
|
||||
|
||||
@Test
|
||||
fun testIsUidNetworkingBlockedByFirewallChains_allowChain() {
|
||||
// With everything disabled by default, verify the return value is false.
|
||||
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
|
||||
assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID1))
|
||||
|
||||
// Enable dozable chain but does not provide allowed list. Verify the network is blocked
|
||||
// for all uids.
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
|
||||
assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID2))
|
||||
|
||||
// Add uid1 to dozable allowed list. Verify the network is not blocked for uid1, while
|
||||
// uid2 is blocked.
|
||||
testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, DOZABLE_MATCH))
|
||||
assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID1))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID2))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,14 +132,14 @@ class BpfNetMapsReaderTest {
|
||||
// for all uids.
|
||||
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
|
||||
assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID1))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID2))
|
||||
|
||||
// Add uid1 to standby allowed list. Verify the network is blocked for uid1, while
|
||||
// uid2 is not blocked.
|
||||
testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, STANDBY_MATCH))
|
||||
assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID2))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,6 +149,54 @@ class BpfNetMapsReaderTest {
|
||||
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true)
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
|
||||
assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1))
|
||||
}
|
||||
|
||||
@IgnoreUpTo(VERSION_CODES.S_V2)
|
||||
@Test
|
||||
fun testIsUidNetworkingBlockedByDataSaver() {
|
||||
// With everything disabled by default, verify the return value is false.
|
||||
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true))
|
||||
|
||||
// Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not
|
||||
// affected.
|
||||
testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
|
||||
|
||||
// Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box
|
||||
// is not affected.
|
||||
testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
|
||||
|
||||
// Add uid1 to happy box as well, verify nothing is changed because penalty box has higher
|
||||
// priority.
|
||||
testUidOwnerMap.updateEntry(
|
||||
S32(TEST_UID1),
|
||||
UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH or HAPPY_BOX_MATCH)
|
||||
)
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
|
||||
|
||||
// Enable doze mode, verify uid3 is blocked even if it is in happy box.
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
|
||||
|
||||
// Disable doze mode and data saver, only uid1 which is in penalty box is blocked.
|
||||
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false)
|
||||
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
|
||||
|
||||
// Make the network non-metered, nothing is blocked.
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID1))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID2))
|
||||
assertFalse(isUidNetworkingBlocked(TEST_UID3))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.content.Context.RECEIVER_NOT_EXPORTED;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
|
||||
import static android.net.ConnectivityManager.TYPE_NONE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
|
||||
@@ -39,6 +46,7 @@ import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
|
||||
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -51,6 +59,7 @@ import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.after;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
@@ -61,7 +70,10 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.net.ConnectivityManager.DataSaverStatusTracker;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
@@ -95,6 +107,7 @@ public class ConnectivityManagerTest {
|
||||
|
||||
@Mock Context mCtx;
|
||||
@Mock IConnectivityManager mService;
|
||||
@Mock NetworkPolicyManager mNpm;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -510,4 +523,54 @@ public class ConnectivityManagerTest {
|
||||
assertNull("ConnectivityManager weak reference still not null after " + attempts
|
||||
+ " attempts", ref.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataSaverStatusTracker() {
|
||||
mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);
|
||||
// Mock proper application info.
|
||||
doReturn(mCtx).when(mCtx).getApplicationContext();
|
||||
final ApplicationInfo mockAppInfo = new ApplicationInfo();
|
||||
mockAppInfo.flags = FLAG_PERSISTENT | FLAG_SYSTEM;
|
||||
doReturn(mockAppInfo).when(mCtx).getApplicationInfo();
|
||||
// Enable data saver.
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_ENABLED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
|
||||
final DataSaverStatusTracker tracker = new DataSaverStatusTracker(mCtx);
|
||||
// Verify the data saver status is correct right after initialization.
|
||||
assertTrue(tracker.getDataSaverEnabled());
|
||||
|
||||
// Verify the tracker register receiver with expected intent filter.
|
||||
final ArgumentCaptor<IntentFilter> intentFilterCaptor =
|
||||
ArgumentCaptor.forClass(IntentFilter.class);
|
||||
verify(mCtx).registerReceiver(
|
||||
any(), intentFilterCaptor.capture(), eq(RECEIVER_NOT_EXPORTED));
|
||||
assertEquals(ACTION_RESTRICT_BACKGROUND_CHANGED,
|
||||
intentFilterCaptor.getValue().getAction(0));
|
||||
|
||||
// Mock data saver status changed event and verify the tracker tracks the
|
||||
// status accordingly.
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_DISABLED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
|
||||
assertFalse(tracker.getDataSaverEnabled());
|
||||
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_WHITELISTED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
|
||||
assertTrue(tracker.getDataSaverEnabled());
|
||||
}
|
||||
|
||||
private <T> void mockService(Class<T> clazz, String name, T service) {
|
||||
doReturn(service).when(mCtx).getSystemService(name);
|
||||
doReturn(name).when(mCtx).getSystemServiceName(clazz);
|
||||
|
||||
// If the test suite uses the inline mock maker library, such as for coverage tests,
|
||||
// then the final version of getSystemService must also be mocked, as the real
|
||||
// method will not be called by the test and null object is returned since no mock.
|
||||
// Otherwise, mocking a final method will fail the test.
|
||||
if (mCtx.getSystemService(clazz) == null) {
|
||||
doReturn(service).when(mCtx).getSystemService(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user