From 47839a0cb4e04052b81d0c0293c2ed0e8627f240 Mon Sep 17 00:00:00 2001 From: Pavel Maltsev Date: Mon, 9 Apr 2018 13:10:11 -0700 Subject: [PATCH 1/3] Remove unwanted capabilitiy from the java-doc Per API council feedback remove unwanted capaibility from public API java docs Bug: 77601789 Test: make docs Test: build and flash Change-Id: Ia7fd6d79546bf99448f8f2bda0370c7230ea4527 --- core/java/android/net/NetworkRequest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 6f812ac38e..bd4a27c2ab 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -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. - *

- * 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. From 488df2dfa09768c99e93a8a6f8f47f45dcefb613 Mon Sep 17 00:00:00 2001 From: dalyk Date: Mon, 5 Mar 2018 12:42:22 -0500 Subject: [PATCH 2/3] Trigger NetworkCallback events when private DNS usage has changed. Relies on events sent from netd in aosp/578162. Test: Added tests to ConnectivityServiceTest. Added a new test class DnsManagerTest. Built a simple app that appears to receive onLinkProperties events correctly upon manual changes to the private DNS settings on a Pixel. Bug: 71828272 Merged-In: I1e6c54ba016f6a165a302bd135a29d9332aaa235 Merged-In: I7705412803fb9aa707a18ae5a1c50292e084d851 Change-Id: I3223c1285a73d5d531c5051ce70007857caa57e3 (cherry picked from commit 7301aa4140baefb549a737f033fc512e87c55692) --- .../android/server/ConnectivityService.java | 65 ++++++ .../server/connectivity/DnsManager.java | 139 +++++++++++- .../server/ConnectivityServiceTest.java | 132 +++++++++++- .../server/connectivity/DnsManagerTest.java | 201 ++++++++++++++++++ 4 files changed, 532 insertions(+), 5 deletions(-) create mode 100644 tests/net/java/com/android/server/connectivity/DnsManagerTest.java diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 59cb135627..6463bed639 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -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); diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index 2a361a02d7..7aaac06024 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -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 pairs for a single netId + private Map, 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> latestDnses = new HashSet<>(); + for (String ipAddress : ipAddresses) { + try { + latestDnses.add(new Pair(hostname, + InetAddress.parseNumericAddress(ipAddress))); + } catch (IllegalArgumentException e) {} + } + // Remove pairs that should not be tracked. + for (Iterator, ValidationStatus>> it = + mValidationMap.entrySet().iterator(); it.hasNext(); ) { + Map.Entry, ValidationStatus> entry = it.next(); + if (!latestDnses.contains(entry.getKey())) { + it.remove(); + } + } + // Add new pairs that should be tracked. + for (Pair p : latestDnses) { + if (!mValidationMap.containsKey(p)) { + mValidationMap.put(p, ValidationStatus.IN_PROGRESS); + } + } + } + + private void updateStatus(PrivateDnsValidationUpdate update) { + Pair 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 mPrivateDnsMap; + private final Map 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))); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b0e11c4e8b..482d6e1fdb 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -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 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 dnsServers) { + assertTrue(callbackObj instanceof LinkProperties); + LinkProperties lp = (LinkProperties) callbackObj; + assertEquals(dnsServers.size(), lp.getDnsServers().size()); + assertTrue(lp.getDnsServers().containsAll(dnsServers)); + } + private static void assertEmpty(T[] ts) { int length = ts.length; assertEquals("expected empty array, but length was " + length, 0, length); diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java new file mode 100644 index 0000000000..bcd8bf3df7 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -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()); + } +} From b136bfd8ebe930480e1cf0b03fe7d99157ef437a Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 2 Apr 2018 21:18:52 +0900 Subject: [PATCH 3/3] Added tests for MultipathPolicyTracker. Test: atest com.android.server.connectivity.MultipathPolicyTrackerTest Bug: b/72631572 Bug: b/72877610 Change-Id: I33d9cd77948ff76008a125e4e2786fbbc2b03032 --- .../MultipathPolicyTrackerTest.java | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java new file mode 100644 index 0000000000..e58811b4c0 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java @@ -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 mConfigChangeReceiverCaptor; + + private MultipathPolicyTracker mTracker; + + private Clock mPreviousRecurrenceRuleClock; + private boolean mRecurrenceRuleClockMocked; + + private void mockService(String serviceName, Class 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 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()); + } +}