Snap for 4713750 from 5f91d5ee942ed2af54349a2a78f635237a84dbb4 to pi-release

Change-Id: Idee2cec3cc7637085e818931fee6048f005a5a94
This commit is contained in:
android-build-team Robot
2018-04-11 07:23:40 +00:00
6 changed files with 893 additions and 12 deletions

View File

@@ -168,11 +168,6 @@ public class NetworkRequest implements Parcelable {
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
* satisfied.
* <p>
* If the given capability was previously added to the list of unwanted capabilities
* then the capability will also be removed from the list of unwanted capabilities.
*
* @see #addUnwantedCapability(int)
*
* @param capability The capability to add.
* @return The builder to facilitate chaining
@@ -184,8 +179,7 @@ public class NetworkRequest implements Parcelable {
}
/**
* Removes (if found) the given capability from this builder instance from both required
* and unwanted capabilities lists.
* Removes (if found) the given capability from this builder instance.
*
* @param capability The capability to remove.
* @return The builder to facilitate chaining.

View File

@@ -52,6 +52,8 @@ import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.IConnectivityManager;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
@@ -140,6 +142,7 @@ import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
@@ -155,6 +158,7 @@ import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.tethering.TetheringDependencies;
import com.android.server.net.BaseNetdEventCallback;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -256,6 +260,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyManager;
private NetworkPolicyManagerInternal mPolicyManagerInternal;
private IIpConnectivityMetrics mIpConnectivityMetrics;
private String mCurrentTcpBufferSizes;
@@ -414,6 +419,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Handle changes in Private DNS settings.
private static final int EVENT_PRIVATE_DNS_SETTINGS_CHANGED = 37;
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -1553,6 +1561,41 @@ public class ConnectivityService extends IConnectivityManager.Stub
return true;
}
@VisibleForTesting
protected final INetdEventCallback mNetdEventCallback = new BaseNetdEventCallback() {
@Override
public void onPrivateDnsValidationEvent(int netId, String ipAddress,
String hostname, boolean validated) {
try {
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_PRIVATE_DNS_VALIDATION_UPDATE,
new PrivateDnsValidationUpdate(netId,
InetAddress.parseNumericAddress(ipAddress),
hostname, validated)));
} catch (IllegalArgumentException e) {
loge("Error parsing ip address in validation event");
}
}
};
@VisibleForTesting
protected void registerNetdEventCallback() {
mIpConnectivityMetrics =
(IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
if (mIpConnectivityMetrics == null) {
Slog.wtf(TAG, "Missing IIpConnectivityMetrics");
}
try {
mIpConnectivityMetrics.addNetdEventCallback(
INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE,
mNetdEventCallback);
} catch (Exception e) {
loge("Error registering netd callback: " + e);
}
}
private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
@@ -1738,6 +1781,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
void systemReady() {
loadGlobalProxy();
registerNetdEventCallback();
synchronized (this) {
mSystemReady = true;
@@ -2288,6 +2332,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
handlePerNetworkPrivateDnsConfig(nai, cfg);
if (networkRequiresValidation(nai)) {
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
}
}
@@ -2312,6 +2359,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateDnses(nai.linkProperties, null, nai.network.netId);
}
private void handlePrivateDnsValidationUpdate(PrivateDnsValidationUpdate update) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetId(update.netId);
if (nai == null) {
return;
}
mDnsManager.updatePrivateDnsValidation(update);
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
private void updateLingerState(NetworkAgentInfo nai, long now) {
// 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
// 2. If the network was lingering and there are now requests, unlinger it.
@@ -3002,6 +3058,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
case EVENT_PRIVATE_DNS_SETTINGS_CHANGED:
handlePrivateDnsSettingsChanged();
break;
case EVENT_PRIVATE_DNS_VALIDATION_UPDATE:
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
}
}
}
@@ -4575,6 +4635,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateRoutes(newLp, oldLp, netId);
updateDnses(newLp, oldLp, netId);
// Make sure LinkProperties represents the latest private DNS status.
// This does not need to be done before updateDnses because the
// LinkProperties are not the source of the private DNS configuration.
// updateDnses will fetch the private DNS configuration from DnsManager.
mDnsManager.updatePrivateDnsStatus(netId, newLp);
// Start or stop clat accordingly to network state.
networkAgent.updateClat(mNetd);

View File

@@ -37,10 +37,10 @@ import android.net.Uri;
import android.net.dns.ResolvUtil;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import com.android.server.connectivity.MockableSystemProperties;
@@ -50,8 +50,12 @@ import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.StringJoiner;
@@ -110,6 +114,7 @@ import java.util.StringJoiner;
*/
public class DnsManager {
private static final String TAG = DnsManager.class.getSimpleName();
private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig();
/* Defaults for resolver parameters. */
private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
@@ -183,11 +188,89 @@ public class DnsManager {
};
}
public static class PrivateDnsValidationUpdate {
final public int netId;
final public InetAddress ipAddress;
final public String hostname;
final public boolean validated;
public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress,
String hostname, boolean validated) {
this.netId = netId;
this.ipAddress = ipAddress;
this.hostname = hostname;
this.validated = validated;
}
}
private static class PrivateDnsValidationStatuses {
enum ValidationStatus {
IN_PROGRESS,
FAILED,
SUCCEEDED
}
// Validation statuses of <hostname, ipAddress> pairs for a single netId
private Map<Pair<String, InetAddress>, ValidationStatus> mValidationMap;
private PrivateDnsValidationStatuses() {
mValidationMap = new HashMap<>();
}
private boolean hasValidatedServer() {
for (ValidationStatus status : mValidationMap.values()) {
if (status == ValidationStatus.SUCCEEDED) {
return true;
}
}
return false;
}
private void updateTrackedDnses(String[] ipAddresses, String hostname) {
Set<Pair<String, InetAddress>> latestDnses = new HashSet<>();
for (String ipAddress : ipAddresses) {
try {
latestDnses.add(new Pair(hostname,
InetAddress.parseNumericAddress(ipAddress)));
} catch (IllegalArgumentException e) {}
}
// Remove <hostname, ipAddress> pairs that should not be tracked.
for (Iterator<Map.Entry<Pair<String, InetAddress>, ValidationStatus>> it =
mValidationMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Pair<String, InetAddress>, ValidationStatus> entry = it.next();
if (!latestDnses.contains(entry.getKey())) {
it.remove();
}
}
// Add new <hostname, ipAddress> pairs that should be tracked.
for (Pair<String, InetAddress> p : latestDnses) {
if (!mValidationMap.containsKey(p)) {
mValidationMap.put(p, ValidationStatus.IN_PROGRESS);
}
}
}
private void updateStatus(PrivateDnsValidationUpdate update) {
Pair<String, InetAddress> p = new Pair(update.hostname,
update.ipAddress);
if (!mValidationMap.containsKey(p)) {
return;
}
if (update.validated) {
mValidationMap.put(p, ValidationStatus.SUCCEEDED);
} else {
mValidationMap.put(p, ValidationStatus.FAILED);
}
}
}
private final Context mContext;
private final ContentResolver mContentResolver;
private final INetworkManagementService mNMS;
private final MockableSystemProperties mSystemProperties;
// TODO: Replace these Maps with SparseArrays.
private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap;
private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
private int mNumDnsEntries;
private int mSampleValidity;
@@ -203,6 +286,7 @@ public class DnsManager {
mNMS = nms;
mSystemProperties = sp;
mPrivateDnsMap = new HashMap<>();
mPrivateDnsValidationMap = new HashMap<>();
// TODO: Create and register ContentObservers to track every setting
// used herein, posting messages to respond to changes.
@@ -214,6 +298,7 @@ public class DnsManager {
public void removeNetwork(Network network) {
mPrivateDnsMap.remove(network.netId);
mPrivateDnsValidationMap.remove(network.netId);
}
public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
@@ -223,6 +308,40 @@ public class DnsManager {
: mPrivateDnsMap.remove(network.netId);
}
public void updatePrivateDnsStatus(int netId, LinkProperties lp) {
// Use the PrivateDnsConfig data pushed to this class instance
// from ConnectivityService.
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
PRIVATE_DNS_OFF);
final boolean useTls = privateDnsCfg.useTls;
final boolean strictMode = privateDnsCfg.inStrictMode();
final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
if (strictMode) {
lp.setUsePrivateDns(true);
lp.setPrivateDnsServerName(tlsHostname);
} else if (useTls) {
// We are in opportunistic mode. Private DNS should be used if there
// is a known DNS-over-TLS validated server.
boolean validated = mPrivateDnsValidationMap.containsKey(netId) &&
mPrivateDnsValidationMap.get(netId).hasValidatedServer();
lp.setUsePrivateDns(validated);
lp.setPrivateDnsServerName(null);
} else {
// Private DNS is disabled.
lp.setUsePrivateDns(false);
lp.setPrivateDnsServerName(null);
}
}
public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) {
final PrivateDnsValidationStatuses statuses =
mPrivateDnsValidationMap.get(update.netId);
if (statuses == null) return;
statuses.updateStatus(update);
}
public void setDnsConfigurationForNetwork(
int netId, LinkProperties lp, boolean isDefaultNetwork) {
final String[] assignedServers = NetworkUtils.makeStrings(lp.getDnsServers());
@@ -238,10 +357,11 @@ public class DnsManager {
//
// At this time we do not attempt to enable Private DNS on non-Internet
// networks like IMS.
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.get(netId);
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
PRIVATE_DNS_OFF);
final boolean useTls = (privateDnsCfg != null) && privateDnsCfg.useTls;
final boolean strictMode = (privateDnsCfg != null) && privateDnsCfg.inStrictMode();
final boolean useTls = privateDnsCfg.useTls;
final boolean strictMode = privateDnsCfg.inStrictMode();
final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
final String[] tlsServers =
strictMode ? NetworkUtils.makeStrings(
@@ -251,6 +371,17 @@ public class DnsManager {
: useTls ? assignedServers // Opportunistic
: new String[0]; // Off
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
if (!mPrivateDnsValidationMap.containsKey(netId)) {
mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses());
}
mPrivateDnsValidationMap.get(netId).updateTrackedDnses(tlsServers, tlsHostname);
} else {
mPrivateDnsValidationMap.remove(netId);
}
Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)",
netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs),
Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers)));

View File

@@ -161,6 +161,7 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -879,6 +880,10 @@ public class ConnectivityServiceTest {
return mMetricsService;
}
@Override
protected void registerNetdEventCallback() {
}
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor;
}
@@ -3777,6 +3782,11 @@ public class ConnectivityServiceTest {
// The default on Android is opportunistic mode ("Automatic").
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
final NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
mCm.requestNetwork(cellRequest, cellNetworkCallback);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle();
// CS tells netd about the empty DNS config for this network.
@@ -3812,6 +3822,14 @@ public class ConnectivityServiceTest {
assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService);
cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
mCellNetworkAgent);
CallbackInfo cbi = cellNetworkCallback.expectCallback(
CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
@@ -3821,6 +3839,7 @@ public class ConnectivityServiceTest {
assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService);
cellNetworkCallback.assertNoCallback();
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
@@ -3833,8 +3852,112 @@ public class ConnectivityServiceTest {
assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService);
cellNetworkCallback.assertNoCallback();
// Can't test strict mode without properly mocking out the DNS lookups.
setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
// Can't test dns configuration for strict mode without properly mocking
// out the DNS lookups, but can test that LinkProperties is updated.
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName());
}
@Test
public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception {
// The default on Android is opportunistic mode ("Automatic").
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
final NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
mCm.requestNetwork(cellRequest, cellNetworkCallback);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle();
LinkProperties lp = new LinkProperties();
mCellNetworkAgent.sendLinkProperties(lp);
mCellNetworkAgent.connect(false);
waitForIdle();
cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
mCellNetworkAgent);
CallbackInfo cbi = cellNetworkCallback.expectCallback(
CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
Set<InetAddress> dnsServers = new HashSet<>();
checkDnsServers(cbi.arg, dnsServers);
// Send a validation event for a server that is not part of the current
// resolver config. The validation event should be ignored.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "", "145.100.185.18", true);
cellNetworkCallback.assertNoCallback();
// Add a dns server to the LinkProperties.
LinkProperties lp2 = new LinkProperties(lp);
lp2.addDnsServer(InetAddress.getByName("145.100.185.16"));
mCellNetworkAgent.sendLinkProperties(lp2);
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
dnsServers.add(InetAddress.getByName("145.100.185.16"));
checkDnsServers(cbi.arg, dnsServers);
// Send a validation event containing a hostname that is not part of
// the current resolver config. The validation event should be ignored.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "hostname", true);
cellNetworkCallback.assertNoCallback();
// Send a validation event where validation failed.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", false);
cellNetworkCallback.assertNoCallback();
// Send a validation event where validation succeeded for a server in
// the current resolver config. A LinkProperties callback with updated
// private dns fields should be sent.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true);
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
checkDnsServers(cbi.arg, dnsServers);
// The private dns fields in LinkProperties should be preserved when
// the network agent sends unrelated changes.
LinkProperties lp3 = new LinkProperties(lp2);
lp3.setMtu(1300);
mCellNetworkAgent.sendLinkProperties(lp3);
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
checkDnsServers(cbi.arg, dnsServers);
assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
// Removing the only validated server should affect the private dns
// fields in LinkProperties.
LinkProperties lp4 = new LinkProperties(lp3);
lp4.removeDnsServer(InetAddress.getByName("145.100.185.16"));
mCellNetworkAgent.sendLinkProperties(lp4);
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
dnsServers.remove(InetAddress.getByName("145.100.185.16"));
checkDnsServers(cbi.arg, dnsServers);
assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
}
private void checkDirectlyConnectedRoutes(Object callbackObj,
@@ -3854,6 +3977,13 @@ public class ConnectivityServiceTest {
assertTrue(observedRoutes.containsAll(expectedRoutes));
}
private static void checkDnsServers(Object callbackObj, Set<InetAddress> dnsServers) {
assertTrue(callbackObj instanceof LinkProperties);
LinkProperties lp = (LinkProperties) callbackObj;
assertEquals(dnsServers.size(), lp.getDnsServers().size());
assertTrue(lp.getDnsServers().containsAll(dnsServers));
}
private static <T> void assertEmpty(T[] ts) {
int length = ts.length;
assertEquals("expected empty array, but length was " + length, 0, length);

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2018, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.Context;
import android.net.LinkProperties;
import android.net.Network;
import android.os.INetworkManagementService;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.test.mock.MockContentResolver;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.connectivity.MockableSystemProperties;
import java.net.InetAddress;
import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link DnsManager}.
*
* Build, install and run with:
* runtest frameworks-net -c com.android.server.connectivity.DnsManagerTest
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DnsManagerTest {
static final int TEST_NETID = 100;
static final int TEST_NETID_ALTERNATE = 101;
static final int TEST_NETID_UNTRACKED = 102;
final boolean IS_DEFAULT = true;
final boolean NOT_DEFAULT = false;
DnsManager mDnsManager;
MockContentResolver mContentResolver;
@Mock Context mCtx;
@Mock INetworkManagementService mNMService;
@Mock MockableSystemProperties mSystemProperties;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContentResolver = new MockContentResolver();
mContentResolver.addProvider(Settings.AUTHORITY,
new FakeSettingsProvider());
when(mCtx.getContentResolver()).thenReturn(mContentResolver);
mDnsManager = new DnsManager(mCtx, mNMService, mSystemProperties);
// Clear the private DNS settings
Settings.Global.putString(mContentResolver,
Settings.Global.PRIVATE_DNS_MODE, "");
Settings.Global.putString(mContentResolver,
Settings.Global.PRIVATE_DNS_SPECIFIER, "");
}
@Test
public void testTrackedValidationUpdates() throws Exception {
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
mDnsManager.updatePrivateDns(new Network(TEST_NETID_ALTERNATE),
mDnsManager.getPrivateDnsConfig());
LinkProperties lp = new LinkProperties();
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
// Send a validation event that is tracked on the alternate netId
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID_ALTERNATE, lp, NOT_DEFAULT);
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE,
InetAddress.parseNumericAddress("4.4.4.4"), "", true));
LinkProperties fixedLp = new LinkProperties(lp);
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
assertFalse(fixedLp.isPrivateDnsActive());
assertNull(fixedLp.getPrivateDnsServerName());
fixedLp = new LinkProperties(lp);
mDnsManager.updatePrivateDnsStatus(TEST_NETID_ALTERNATE, fixedLp);
assertTrue(fixedLp.isPrivateDnsActive());
assertNull(fixedLp.getPrivateDnsServerName());
// Switch to strict mode
Settings.Global.putString(mContentResolver,
Settings.Global.PRIVATE_DNS_MODE,
PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
Settings.Global.putString(mContentResolver,
Settings.Global.PRIVATE_DNS_SPECIFIER, "strictmode.com");
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
fixedLp = new LinkProperties(lp);
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
assertTrue(fixedLp.isPrivateDnsActive());
assertEquals("strictmode.com", fixedLp.getPrivateDnsServerName());
fixedLp = new LinkProperties(lp);
}
@Test
public void testIgnoreUntrackedValidationUpdates() throws Exception {
// The PrivateDnsConfig map is empty, so no validation events will
// be tracked.
LinkProperties lp = new LinkProperties();
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Validation event has untracked netId
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Validation event has untracked ipAddress
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("4.4.4.4"), "", true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Validation event has untracked hostname
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "hostname",
true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Validation event failed
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", false));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Network removed
mDnsManager.removeNetwork(new Network(TEST_NETID));
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
// Turn private DNS mode off
Settings.Global.putString(mContentResolver,
Settings.Global.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OFF);
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
assertFalse(lp.isPrivateDnsActive());
assertNull(lp.getPrivateDnsServerName());
}
}

View File

@@ -0,0 +1,360 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.util.DataUnit;
import android.util.RecurrenceRule;
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.net.NetworkStatsManagerInternal;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.time.Clock;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MultipathPolicyTrackerTest {
private static final Network TEST_NETWORK = new Network(123);
private static final int POLICY_SNOOZED = -100;
@Mock private Context mContext;
@Mock private Resources mResources;
@Mock private Handler mHandler;
@Mock private MultipathPolicyTracker.Dependencies mDeps;
@Mock private Clock mClock;
@Mock private ConnectivityManager mCM;
@Mock private NetworkPolicyManager mNPM;
@Mock private NetworkStatsManager mStatsManager;
@Mock private NetworkPolicyManagerInternal mNPMI;
@Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal;
@Mock private TelephonyManager mTelephonyManager;
private MockContentResolver mContentResolver;
private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor;
private MultipathPolicyTracker mTracker;
private Clock mPreviousRecurrenceRuleClock;
private boolean mRecurrenceRuleClockMocked;
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
when(mContext.getSystemService(serviceName)).thenReturn(service);
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mPreviousRecurrenceRuleClock = RecurrenceRule.sClock;
RecurrenceRule.sClock = mClock;
mRecurrenceRuleClockMocked = true;
mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mContext.registerReceiverAsUser(mConfigChangeReceiverCaptor.capture(),
any(), argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any()))
.thenReturn(null);
when(mDeps.getClock()).thenReturn(mClock);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
mContentResolver = Mockito.spy(new MockContentResolver(mContext));
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Settings.Global.clearProviderForTest();
when(mContext.getContentResolver()).thenReturn(mContentResolver);
mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM);
mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM);
mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager);
mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI);
LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal);
mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps);
}
@After
public void tearDown() {
// Avoid setting static clock to null (which should normally not be the case)
// if MockitoAnnotations.initMocks threw an exception
if (mRecurrenceRuleClockMocked) {
RecurrenceRule.sClock = mPreviousRecurrenceRuleClock;
}
mRecurrenceRuleClockMocked = false;
}
private void setDefaultQuotaGlobalSetting(long setting) {
Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
(int) setting);
}
private void testGetMultipathPreference(
long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
// TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly.
final ZonedDateTime now = ZonedDateTime.ofInstant(
Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault());
final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS);
when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli());
when(mClock.instant()).thenReturn(now.toInstant());
when(mClock.getZone()).thenReturn(ZoneId.systemDefault());
// Setup plan quota
when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
.thenReturn(subscriptionQuota);
// Setup user policy warning / limit
if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) {
final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z");
final RecurrenceRule recurrenceRule = new RecurrenceRule(
ZonedDateTime.ofInstant(
recurrenceStart,
ZoneId.systemDefault()),
null /* end */,
Period.ofMonths(1));
final boolean snoozeWarning = policyWarning == POLICY_SNOOZED;
final boolean snoozeLimit = policyLimit == POLICY_SNOOZED;
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] {
new NetworkPolicy(
NetworkTemplate.buildTemplateMobileWildcard(),
recurrenceRule,
snoozeWarning ? 0 : policyWarning,
snoozeLimit ? 0 : policyLimit,
snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
SNOOZE_NEVER,
true /* metered */,
false /* inferred */)
});
} else {
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
}
// Setup default quota in settings and resources
if (defaultGlobalSetting > 0) {
setDefaultQuotaGlobalSetting(defaultGlobalSetting);
}
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) defaultResSetting);
when(mNetworkStatsManagerInternal.getNetworkTotalBytes(
any(),
eq(startOfDay.toInstant().toEpochMilli()),
eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday);
ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
mTracker.start();
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
// Simulate callback after capability changes
final NetworkCapabilities capabilities = new NetworkCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(new StringNetworkSpecifier("234"));
if (!roaming) {
capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
networkCallback.getValue().onCapabilitiesChanged(
TEST_NETWORK,
capabilities);
}
@Test
public void testGetMultipathPreference_SubscriptionQuota() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
LIMIT_DISABLED,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
public void testGetMultipathPreference_UserWarningQuota() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
// 29 days from Apr. 2nd to May 1st
DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */,
LIMIT_DISABLED,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
false /* roaming */);
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
public void testGetMultipathPreference_SnoozedWarningQuota() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
// 29 days from Apr. 2nd to May 1st
POLICY_SNOOZED /* policyWarning */,
DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
false /* roaming */);
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
public void testGetMultipathPreference_SnoozedBothQuota() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
// 29 days from Apr. 2nd to May 1st
POLICY_SNOOZED /* policyWarning */,
POLICY_SNOOZED /* policyLimit */,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
false /* roaming */);
// Default global setting should be used: 12 - 7 = 5
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
}
@Test
public void testGetMultipathPreference_SettingChanged() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
WARNING_DISABLED,
LIMIT_DISABLED,
-1 /* defaultGlobalSetting */,
DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */,
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
// Update setting
setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
mTracker.mSettingsObserver.onChange(
false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES));
// Callback must have been re-registered with new setting
verify(mStatsManager, times(1)).unregisterUsageCallback(any());
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
public void testGetMultipathPreference_ResourceChanged() {
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
WARNING_DISABLED,
LIMIT_DISABLED,
-1 /* defaultGlobalSetting */,
DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */,
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue();
assertNotNull(configChangeReceiver);
configChangeReceiver.onReceive(mContext, new Intent());
// Uses the new setting (16 - 2 = 14MB)
verify(mStatsManager, times(1)).registerUsageCallback(
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
}
}