Update multiple validation result to ConnectivityService

Once a network is determined to have partial connectivity, it
cannot go back to full connectivity without a disconnect. This
is because NetworkMonitor can only communicate either
PARTIAL_CONNECTIVITY or VALID, but not both. Thus, multiple
validation results allow ConnectivityService to know the real
network status.

Bug: 129662877
Bug: 130683832
Test: atest FrameworksNetTests
Test: atest NetworkStackTests
Test: atest --generate-new-metrics 50
NetworkStackTests:com.android.server.connectivity.NetworkMonitorTest
Test: Simulate partial connectvitiy
Change-Id: I406c9368617c03a2dd3ab15fb1f6dbf539d7c714
Merged-In: I243db4c406cca826e803c8035268bc0c6e6e01e2
(cherry picked from commit 4532abd4d2)
This commit is contained in:
Chiachang Wang
2019-05-23 22:57:18 -07:00
parent da7156d13d
commit 8d573213af
4 changed files with 404 additions and 96 deletions

View File

@@ -24,8 +24,13 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.DnsResolver.FLAG_EMPTY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -69,7 +74,6 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.DnsResolver;
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.LinkProperties;
import android.net.Network;
@@ -277,7 +281,7 @@ public class NetworkMonitor extends StateMachine {
private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
// Delay between reevaluations once a captive portal has been found.
private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
private static final int NETWORK_VALIDATION_RESULT_INVALID = 0;
private String mPrivateDnsProviderHostname = "";
private final Context mContext;
@@ -348,7 +352,8 @@ public class NetworkMonitor extends StateMachine {
private long mLastProbeTime;
// Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
private boolean mCollectDataStallMetrics;
private boolean mAcceptPartialConnectivity;
private boolean mAcceptPartialConnectivity = false;
private final EvaluationState mEvaluationState = new EvaluationState();
public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
SharedLog validationLog) {
@@ -601,7 +606,8 @@ public class NetworkMonitor extends StateMachine {
case APP_RETURN_UNWANTED:
mDontDisplaySigninNotification = true;
mUserDoesNotWant = true;
notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
mEvaluationState.reportEvaluationResult(
NETWORK_VALIDATION_RESULT_INVALID, null);
// TODO: Should teardown network.
mUidResponsibleForReeval = 0;
transitionTo(mEvaluatingState);
@@ -653,7 +659,7 @@ public class NetworkMonitor extends StateMachine {
// re-evaluating and get the result of partial connectivity, ProbingState will
// disable HTTPS probe and transition to EvaluatingPrivateDnsState.
case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
mAcceptPartialConnectivity = true;
maybeDisableHttpsProbing(true /* acceptPartial */);
break;
case EVENT_LINK_PROPERTIES_CHANGED:
mLinkProperties = (LinkProperties) message.obj;
@@ -677,7 +683,14 @@ public class NetworkMonitor extends StateMachine {
public void enter() {
maybeLogEvaluationResult(
networkEventType(validationStage(), EvaluationResult.VALIDATED));
notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
// If the user has accepted that and HTTPS probing is disabled, then mark the network
// as validated and partial so that settings can keep informing the user that the
// connection is limited.
int result = NETWORK_VALIDATION_RESULT_VALID;
if (!mUseHttps && mAcceptPartialConnectivity) {
result |= NETWORK_VALIDATION_RESULT_PARTIAL;
}
mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */);
mValidations++;
}
@@ -820,6 +833,9 @@ public class NetworkMonitor extends StateMachine {
}
mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
mEvaluateAttempts = 0;
// Reset all current probe results to zero, but retain current validation state until
// validation succeeds or fails.
mEvaluationState.clearProbeResults();
}
@Override
@@ -875,8 +891,7 @@ public class NetworkMonitor extends StateMachine {
// 1. Network is connected and finish the network validation.
// 2. NetworkMonitor detects network is partial connectivity and user accepts it.
case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
mAcceptPartialConnectivity = true;
mUseHttps = false;
maybeDisableHttpsProbing(true /* acceptPartial */);
transitionTo(mEvaluatingPrivateDnsState);
return HANDLED;
default:
@@ -1019,6 +1034,8 @@ public class NetworkMonitor extends StateMachine {
mPrivateDnsConfig = null;
validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
}
mEvaluationState.reportProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
(mPrivateDnsConfig != null) /* succeeded */);
}
private void notifyPrivateDnsConfigResolved() {
@@ -1030,8 +1047,8 @@ public class NetworkMonitor extends StateMachine {
}
private void handlePrivateDnsEvaluationFailure() {
notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
null /* redirectUrl */);
// Queue up a re-evaluation with backoff.
//
// TODO: Consider abandoning this state after a few attempts and
@@ -1050,21 +1067,22 @@ public class NetworkMonitor extends StateMachine {
final String host = UUID.randomUUID().toString().substring(0, 8)
+ oneTimeHostnameSuffix;
final Stopwatch watch = new Stopwatch().start();
boolean success = false;
long time;
try {
final InetAddress[] ips = mNetwork.getAllByName(host);
final long time = watch.stop();
time = watch.stop();
final String strIps = Arrays.toString(ips);
final boolean success = (ips != null && ips.length > 0);
success = (ips != null && ips.length > 0);
validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
return success;
} catch (UnknownHostException uhe) {
final long time = watch.stop();
time = watch.stop();
validationLog(PROBE_PRIVDNS, host,
String.format("%dms - Error: %s", time, uhe.getMessage()));
logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
}
return false;
logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
mEvaluationState.reportProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success);
return success;
}
}
@@ -1106,22 +1124,24 @@ public class NetworkMonitor extends StateMachine {
// state (even if no Private DNS validation required).
transitionTo(mEvaluatingPrivateDnsState);
} else if (probeResult.isPortal()) {
notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
probeResult.redirectUrl);
mLastPortalProbeResult = probeResult;
transitionTo(mCaptivePortalState);
} else if (probeResult.isPartialConnectivity()) {
logNetworkEvent(NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY);
notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY,
probeResult.redirectUrl);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
null /* redirectUrl */);
// Check if disable https probing needed.
maybeDisableHttpsProbing(mAcceptPartialConnectivity);
if (mAcceptPartialConnectivity) {
mUseHttps = false;
transitionTo(mEvaluatingPrivateDnsState);
} else {
transitionTo(mWaitingForNextProbeState);
}
} else {
logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
null /* redirectUrl */);
transitionTo(mWaitingForNextProbeState);
}
return HANDLED;
@@ -1469,10 +1489,13 @@ public class NetworkMonitor extends StateMachine {
final CaptivePortalProbeResult result;
if (pacUrl != null) {
result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
} else if (mUseHttps) {
// Probe results are reported inside sendParallelHttpProbes.
result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
} else {
result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
}
long endTime = SystemClock.elapsedRealtime();
@@ -1484,6 +1507,7 @@ public class NetworkMonitor extends StateMachine {
log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
+ " isPortal()=" + result.isPortal()
+ " RedirectUrl=" + result.redirectUrl
+ " isPartialConnectivity()=" + result.isPartialConnectivity()
+ " Time=" + (endTime - startTime) + "ms");
return result;
@@ -1498,6 +1522,10 @@ public class NetworkMonitor extends StateMachine {
// Only do this if HttpURLConnection is about to, to avoid any potentially
// unnecessary resolution.
final String host = (proxy != null) ? proxy.getHost() : url.getHost();
// This method cannot safely report probe results because it might not be running on the
// state machine thread. Reporting results here would cause races and potentially send
// information to callers that does not make sense because the state machine has already
// changed state.
sendDnsProbe(host);
return sendHttpProbe(url, probeType, null);
}
@@ -1682,10 +1710,12 @@ public class NetworkMonitor extends StateMachine {
// Look for a conclusive probe result first.
if (httpResult.isPortal()) {
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult);
return httpResult;
}
// httpsResult.isPortal() is not expected, but check it nonetheless.
if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult);
return httpsResult;
}
// If a fallback method exists, use it to retry portal detection.
@@ -1695,6 +1725,7 @@ public class NetworkMonitor extends StateMachine {
CaptivePortalProbeResult fallbackProbeResult = null;
if (fallbackUrl != null) {
fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
if (fallbackProbeResult.isPortal()) {
return fallbackProbeResult;
}
@@ -1702,10 +1733,15 @@ public class NetworkMonitor extends StateMachine {
// Otherwise wait until http and https probes completes and use their results.
try {
httpProbe.join();
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result());
if (httpProbe.result().isPortal()) {
return httpProbe.result();
}
httpsProbe.join();
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result());
final boolean isHttpSuccessful =
(httpProbe.result().isSuccessful()
|| (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
@@ -2024,4 +2060,79 @@ public class NetworkMonitor extends StateMachine {
return result;
}
// Class to keep state of evaluation results and probe results.
// The main purpose is to ensure NetworkMonitor can notify ConnectivityService of probe results
// as soon as they happen, without triggering any other changes. This requires keeping state on
// the most recent evaluation result. Calling reportProbeResult will ensure that the results
// reported to ConnectivityService contain the previous evaluation result, and thus won't
// trigger a validation or partial connectivity state change.
@VisibleForTesting
protected class EvaluationState {
// The latest validation result for this network. This is a bitmask of
// INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants.
private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID;
// Indicates which probes have completed since clearProbeResults was called.
// This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
private int mProbeResults = 0;
// The latest redirect URL.
private String mRedirectUrl;
protected void clearProbeResults() {
mProbeResults = 0;
}
// Probe result for http probe should be updated from reportHttpProbeResult().
protected void reportProbeResult(int probeResult, boolean succeeded) {
if (succeeded) {
mProbeResults |= probeResult;
} else {
mProbeResults &= ~probeResult;
}
notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
}
protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
mEvaluationResult = result;
mRedirectUrl = redirectUrl;
notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
}
protected int getNetworkTestResult() {
return mEvaluationResult | mProbeResults;
}
}
@VisibleForTesting
protected EvaluationState getEvaluationState() {
return mEvaluationState;
}
private void maybeDisableHttpsProbing(boolean acceptPartial) {
mAcceptPartialConnectivity = acceptPartial;
// Ignore https probe in next validation if user accept partial connectivity on a partial
// connectivity network.
if (((mEvaluationState.getNetworkTestResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0)
&& mAcceptPartialConnectivity) {
mUseHttps = false;
}
}
// Report HTTP, HTTP or FALLBACK probe result.
@VisibleForTesting
protected void reportHttpProbeResult(int probeResult,
@NonNull final CaptivePortalProbeResult result) {
boolean succeeded = result.isSuccessful();
// The success of a HTTP probe does not tell us whether the DNS probe succeeded.
// The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that
// method cannot report the result of the DNS probe because that it could be running
// on a different thread which is racing with the main state machine thread. So, if
// an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an
// HTTP or HTTPS probe failed, don't assume that DNS is not working.
// TODO: fix this.
if (succeeded) {
probeResult |= NETWORK_VALIDATION_PROBE_DNS;
}
mEvaluationState.reportProbeResult(probeResult, succeeded);
}
}

View File

@@ -17,9 +17,13 @@
package com.android.server.connectivity;
import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
@@ -98,6 +102,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.verification.VerificationWithTimeout;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -149,6 +154,19 @@ public class NetworkMonitorTest {
private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
private static final String TEST_MCCMNC = "123456";
private static final int VALIDATION_RESULT_INVALID = 0;
private static final int VALIDATION_RESULT_PORTAL = 0;
private static final String TEST_REDIRECT_URL = "android.com";
private static final int VALIDATION_RESULT_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_HTTP
| NETWORK_VALIDATION_RESULT_PARTIAL;
private static final int VALIDATION_RESULT_FALLBACK_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_FALLBACK
| NETWORK_VALIDATION_RESULT_PARTIAL;
private static final int VALIDATION_RESULT_VALID = NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_HTTPS
| NETWORK_VALIDATION_RESULT_VALID;
private static final int RETURN_CODE_DNS_SUCCESS = 0;
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
@@ -472,8 +490,7 @@ public class NetworkMonitorTest {
public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
runPortalNetworkTest();
runPortalNetworkTest(VALIDATION_RESULT_PORTAL);
}
@Test
@@ -489,8 +506,7 @@ public class NetworkMonitorTest {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setPortal302(mFallbackConnection);
runPortalNetworkTest();
runPortalNetworkTest(VALIDATION_RESULT_INVALID);
}
@Test
@@ -518,7 +534,7 @@ public class NetworkMonitorTest {
when(mRandom.nextInt()).thenReturn(2);
// First check always uses the first fallback URL: inconclusive
final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID);
final NetworkMonitor monitor = runNetworkTest(VALIDATION_RESULT_INVALID);
assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
verify(mFallbackConnection, times(1)).getResponseCode();
verify(mOtherFallbackConnection, never()).getResponseCode();
@@ -548,8 +564,7 @@ public class NetworkMonitorTest {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setPortal302(mOtherFallbackConnection);
runPortalNetworkTest();
runPortalNetworkTest(VALIDATION_RESULT_INVALID);
verify(mOtherFallbackConnection, times(1)).getResponseCode();
verify(mFallbackConnection, never()).getResponseCode();
}
@@ -572,7 +587,7 @@ public class NetworkMonitorTest {
set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
// HTTPS failed, fallback spec went through -> partial connectivity
runPartialConnectivityNetworkTest();
runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
verify(mOtherFallbackConnection, times(1)).getResponseCode();
verify(mFallbackConnection, never()).getResponseCode();
}
@@ -581,8 +596,7 @@ public class NetworkMonitorTest {
public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
setupFallbackSpec();
set302(mOtherFallbackConnection, "http://login.portal.example.com");
runPortalNetworkTest();
runPortalNetworkTest(VALIDATION_RESULT_INVALID);
}
@Test
@@ -591,7 +605,7 @@ public class NetworkMonitorTest {
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
runNotPortalNetworkTest();
runNoValidationNetworkTest();
}
@Test
@@ -677,7 +691,8 @@ public class NetworkMonitorTest {
@Test
public void testNoInternetCapabilityValidated() throws Exception {
runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID,
getGeneralVerification());
verify(mCleartextDnsNetwork, never()).openConnection(any());
}
@@ -713,10 +728,11 @@ public class NetworkMonitorTest {
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
reset(mCallbacks);
nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
.notifyNetworkTested(eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP
| NETWORK_VALIDATION_RESULT_VALID), any());
assertEquals(0, mRegisteredReceivers.size());
}
@@ -730,7 +746,8 @@ public class NetworkMonitorTest {
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
.notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
eq(null));
}
@Test
@@ -743,38 +760,47 @@ public class NetworkMonitorTest {
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
.notifyNetworkTested(
eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
eq(null));
// Fix DNS and retry, expect validation to succeed.
reset(mCallbacks);
mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
wnm.forceReevaluation(Process.myUid());
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
.notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
eq(null));
// Change configuration to an invalid DNS name, expect validation to fail.
reset(mCallbacks);
mFakeDns.setAnswer("dns.bad", new String[0]);
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
// Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe
// notification.
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
.notifyNetworkTested(eq(VALIDATION_RESULT_VALID), eq(null));
// Change configuration back to working again, but make private DNS not work.
// Expect validation to fail.
reset(mCallbacks);
mFakeDns.setNonBypassPrivateDnsWorking(false);
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google",
new InetAddress[0]));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
.notifyNetworkTested(
eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
eq(null));
// Make private DNS work again. Expect validation to succeed.
reset(mCallbacks);
mFakeDns.setNonBypassPrivateDnsWorking(true);
wnm.forceReevaluation(Process.myUid());
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
.notifyNetworkTested(
eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS), eq(null));
}
@Test
@@ -834,12 +860,14 @@ public class NetworkMonitorTest {
public void testIgnoreHttpsProbe() throws Exception {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 204);
// Expect to send HTTP, HTTPS, FALLBACK probe and evaluation result notifications to CS.
final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PARTIAL);
final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
reset(mCallbacks);
nm.setAcceptPartialConnectivity();
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any());
// Expect to update evaluation result notifications to CS.
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
eq(VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID), eq(null));
}
@Test
@@ -847,12 +875,13 @@ public class NetworkMonitorTest {
setStatus(mHttpsConnection, 500);
setStatus(mHttpConnection, 204);
setStatus(mFallbackConnection, 500);
runPartialConnectivityNetworkTest();
runPartialConnectivityNetworkTest(VALIDATION_RESULT_PARTIAL);
reset(mCallbacks);
setStatus(mHttpsConnection, 500);
setStatus(mHttpConnection, 500);
setStatus(mFallbackConnection, 204);
runPartialConnectivityNetworkTest();
runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
}
private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
@@ -896,6 +925,82 @@ public class NetworkMonitorTest {
}
}
@Test
public void testNotifyNetwork_WithforceReevaluation() throws Exception {
final NetworkMonitor nm = runValidatedNetworkTest();
// Verify forceReevalution will not reset the validation result but only probe result until
// getting the validation result.
reset(mCallbacks);
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setStatus(mFallbackConnection, 204);
nm.forceReevaluation(Process.myUid());
final ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
// Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(4))
.notifyNetworkTested(intCaptor.capture(), any());
List<Integer> intArgs = intCaptor.getAllValues();
assertEquals(Integer.valueOf(NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_FALLBACK | NETWORK_VALIDATION_RESULT_VALID),
intArgs.get(0));
assertTrue((intArgs.get(1) & NETWORK_VALIDATION_RESULT_VALID) != 0);
assertTrue((intArgs.get(2) & NETWORK_VALIDATION_RESULT_VALID) != 0);
assertTrue((intArgs.get(3) & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
assertTrue((intArgs.get(3) & NETWORK_VALIDATION_RESULT_VALID) == 0);
}
@Test
public void testEvaluationState_clearProbeResults() throws Exception {
final NetworkMonitor nm = runValidatedNetworkTest();
nm.getEvaluationState().clearProbeResults();
// Verify probe results are all reset and only evaluation result left.
assertEquals(NETWORK_VALIDATION_RESULT_VALID,
nm.getEvaluationState().getNetworkTestResult());
}
@Test
public void testEvaluationState_reportProbeResult() throws Exception {
final NetworkMonitor nm = runValidatedNetworkTest();
reset(mCallbacks);
nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.SUCCESS);
// Verify result should be appended and notifyNetworkTested callback is triggered once.
assertEquals(nm.getEvaluationState().getNetworkTestResult(),
VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_HTTP);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_HTTP), any());
nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.FAILED);
// Verify DNS probe result should not be cleared.
assertTrue((nm.getEvaluationState().getNetworkTestResult() & NETWORK_VALIDATION_PROBE_DNS)
== NETWORK_VALIDATION_PROBE_DNS);
}
@Test
public void testEvaluationState_reportEvaluationResult() throws Exception {
final NetworkMonitor nm = runValidatedNetworkTest();
nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
null /* redirectUrl */);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
| NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
nm.getEvaluationState().reportEvaluationResult(
NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
null /* redirectUrl */);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID,
TEST_REDIRECT_URL);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
eq(TEST_REDIRECT_URL));
}
private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
for (int i = 0; i < count; i++) {
wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -954,39 +1059,64 @@ public class NetworkMonitorTest {
eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
}
private void runPortalNetworkTest() {
runNetworkTest(NETWORK_TEST_RESULT_INVALID);
private void runPortalNetworkTest(int result) {
// The network test event will be triggered twice with the same result. Expect to capture
// the second one with direct url.
runPortalNetworkTest(result,
(VerificationWithTimeout) timeout(HANDLER_TIMEOUT_MS).times(2));
}
private void runPortalNetworkTest(int result, VerificationWithTimeout mode) {
runNetworkTest(result, mode);
assertEquals(1, mRegisteredReceivers.size());
assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
}
private void runNotPortalNetworkTest() {
runNetworkTest(NETWORK_TEST_RESULT_VALID);
runNetworkTest(VALIDATION_RESULT_VALID);
assertEquals(0, mRegisteredReceivers.size());
assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
}
private void runNoValidationNetworkTest() {
runNetworkTest(NETWORK_VALIDATION_RESULT_VALID);
assertEquals(0, mRegisteredReceivers.size());
assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
}
private void runFailedNetworkTest() {
runNetworkTest(NETWORK_TEST_RESULT_INVALID);
runNetworkTest(VALIDATION_RESULT_INVALID);
assertEquals(0, mRegisteredReceivers.size());
assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
}
private void runPartialConnectivityNetworkTest() {
runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
private void runPartialConnectivityNetworkTest(int result) {
runNetworkTest(result);
assertEquals(0, mRegisteredReceivers.size());
assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
}
private NetworkMonitor runValidatedNetworkTest() throws Exception {
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
// Expect to send HTTPs and evaluation results.
return runNetworkTest(VALIDATION_RESULT_VALID);
}
private NetworkMonitor runNetworkTest(int testResult) {
return runNetworkTest(METERED_CAPABILITIES, testResult);
return runNetworkTest(METERED_CAPABILITIES, testResult, getGeneralVerification());
}
private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
private NetworkMonitor runNetworkTest(int testResult, VerificationWithTimeout mode) {
return runNetworkTest(METERED_CAPABILITIES, testResult, mode);
}
private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult,
VerificationWithTimeout mode) {
final NetworkMonitor monitor = makeMonitor(nc);
monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
try {
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
verify(mCallbacks, mode)
.notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
} catch (RemoteException e) {
fail("Unexpected exception: " + e);
@@ -1018,5 +1148,10 @@ public class NetworkMonitorTest {
stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
}
}
private VerificationWithTimeout getGeneralVerification() {
return (VerificationWithTimeout) timeout(HANDLER_TIMEOUT_MS).atLeastOnce();
}
}

View File

@@ -25,8 +25,8 @@ import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -2605,21 +2605,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
final boolean partialConnectivity =
(msg.arg1 == NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY)
|| (nai.networkMisc.acceptPartialConnectivity
&& nai.partialConnectivity);
// Once a network is determined to have partial connectivity, it cannot
// go back to full connectivity without a disconnect. This is because
// NetworkMonitor can only communicate either PARTIAL_CONNECTIVITY or VALID,
// but not both.
// TODO: Provide multi-testResult to improve the communication between
// ConnectivityService and NetworkMonitor, so that ConnectivityService could
// know the real status of network.
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
(partialConnectivity && !nai.partialConnectivity);
(wasPartial != nai.partialConnectivity);
final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
@@ -2649,21 +2640,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
// Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
// valid.
// Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
// notifications if network becomes valid.
mNotifier.clearNotification(nai.network.netId,
NotificationType.NO_INTERNET);
mNotifier.clearNotification(nai.network.netId,
NotificationType.LOST_INTERNET);
mNotifier.clearNotification(nai.network.netId,
NotificationType.PARTIAL_CONNECTIVITY);
}
} else if (partialConnectivityChanged) {
nai.partialConnectivity = partialConnectivity;
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
Bundle redirectUrlBundle = new Bundle();
redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
// TODO: Evaluate to update partial connectivity to status to NetworkAgent.
nai.asyncChannel.sendMessage(
NetworkAgent.CMD_REPORT_NETWORK_STATUS,
(valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
@@ -3443,6 +3436,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Inform NetworkMonitor that partial connectivity is acceptable. This will likely
// result in a partial connectivity result which will be processed by
// maybeHandleNetworkMonitorMessage.
//
// TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored
// per network. Therefore, NetworkMonitor may still do https probe.
try {
nai.networkMonitor().setAcceptPartialConnectivity();
} catch (RemoteException e) {

View File

@@ -30,9 +30,12 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -443,6 +446,16 @@ public class ConnectivityServiceTest {
}
private class MockNetworkAgent {
private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS
| NETWORK_VALIDATION_PROBE_HTTP
| NETWORK_VALIDATION_PROBE_HTTPS;
private static final int VALIDATION_RESULT_VALID = VALIDATION_RESULT_BASE
| NETWORK_VALIDATION_RESULT_VALID;
private static final int VALIDATION_RESULT_PARTIAL = VALIDATION_RESULT_BASE
| NETWORK_VALIDATION_PROBE_FALLBACK
| NETWORK_VALIDATION_RESULT_PARTIAL;
private static final int VALIDATION_RESULT_INVALID = 0;
private final INetworkMonitor mNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
@@ -460,17 +473,17 @@ public class ConnectivityServiceTest {
private String mRedirectUrl;
private INetworkMonitorCallbacks mNmCallbacks;
private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
private int mNmValidationResult = VALIDATION_RESULT_BASE;
private String mNmValidationRedirectUrl = null;
private boolean mNmProvNotificationRequested = false;
void setNetworkValid() {
mNmValidationResult = NETWORK_TEST_RESULT_VALID;
mNmValidationResult = VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
}
void setNetworkInvalid() {
mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
mNmValidationResult = VALIDATION_RESULT_INVALID;
mNmValidationRedirectUrl = null;
}
@@ -480,7 +493,12 @@ public class ConnectivityServiceTest {
}
void setNetworkPartial() {
mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
mNmValidationResult = VALIDATION_RESULT_PARTIAL;
mNmValidationRedirectUrl = null;
}
void setNetworkPartialValid() {
mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
}
@@ -597,7 +615,7 @@ public class ConnectivityServiceTest {
private void onValidationRequested() {
try {
if (mNmProvNotificationRequested
&& mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
&& mNmValidationResult == VALIDATION_RESULT_VALID) {
mNmCallbacks.hideProvisioningNotification();
mNmProvNotificationRequested = false;
}
@@ -2651,7 +2669,7 @@ public class ConnectivityServiceTest {
// With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
// probe.
mWiFiNetworkAgent.setNetworkValid();
mWiFiNetworkAgent.setNetworkPartialValid();
// If the user chooses yes to use this partial connectivity wifi, switch the default
// network to wifi and check if wifi becomes valid or not.
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -2748,6 +2766,54 @@ public class ConnectivityServiceTest {
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
}
@Test
public void testCaptivePortalOnPartialConnectivity() throws RemoteException {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
final TestNetworkCallback validatedCallback = new TestNetworkCallback();
final NetworkRequest validatedRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_VALIDATED).build();
mCm.registerNetworkCallback(validatedRequest, validatedCallback);
// Bring up a network with a captive portal.
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
// Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
.launchCaptivePortalApp();
// Report that the captive portal is dismissed with partial connectivity, and check that
// callbacks are fired.
mWiFiNetworkAgent.setNetworkPartial();
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
waitForIdle();
captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
mWiFiNetworkAgent);
// Report partial connectivity is accepted.
mWiFiNetworkAgent.setNetworkPartialValid();
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
false /* always */);
waitForIdle();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
NetworkCapabilities nc =
validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
mWiFiNetworkAgent);
mCm.unregisterNetworkCallback(captivePortalCallback);
mCm.unregisterNetworkCallback(validatedCallback);
}
@Test
public void testCaptivePortal() {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();