diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 20c216826531b..2dacf8f460bb2 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -120,12 +120,17 @@ public abstract class NetworkAgent extends Handler { * either a bad network configuration (no internet link) or captive portal. * * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK} + * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String} + * representing URL that Internet probe was redirect to, if it was redirected, + * or mapping to {@code null} otherwise. */ public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; public static final int VALID_NETWORK = 1; public static final int INVALID_NETWORK = 2; + public static String REDIRECT_URL_KEY = "redirect URL"; + /** * Sent by the NetworkAgent to ConnectivityService to indicate this network was * explicitly selected. This should be sent before the NetworkInfo is marked @@ -283,11 +288,12 @@ public abstract class NetworkAgent extends Handler { break; } case CMD_REPORT_NETWORK_STATUS: { + String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY); if (VDBG) { log("CMD_REPORT_NETWORK_STATUS(" + - (msg.arg1 == VALID_NETWORK ? "VALID)" : "INVALID)")); + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl); } - networkStatus(msg.arg1); + networkStatus(msg.arg1, redirectUrl); break; } case CMD_SAVE_ACCEPT_UNVALIDATED: { @@ -443,8 +449,12 @@ public abstract class NetworkAgent extends Handler { * * This may be called multiple times as the network status changes and may * generate false negatives if we lose ip connectivity before the link is torn down. + * + * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}. + * @param redirectUrl If the Internet probe was redirected, this is the destination it was + * redirected to, otherwise {@code null}. */ - protected void networkStatus(int status) { + protected void networkStatus(int status, String redirectUrl) { } /** diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 428e192bb055a..2ccc3fe80edf9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2025,11 +2025,15 @@ public class ConnectivityService extends IConnectivityManager.Stub default: return false; case NetworkMonitor.EVENT_NETWORK_TESTED: { - NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; - if (isLiveNetworkAgent(nai, msg.what)) { + final NetworkAgentInfo nai; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(msg.arg2); + } + if (nai != null) { final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); - if (DBG) log(nai.name() + " validation " + (valid ? " passed" : "failed")); + if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") + + (msg.obj == null ? "" : " with redirect to " + (String)msg.obj)); if (valid != nai.lastValidated) { final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; @@ -2040,10 +2044,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } updateInetCondition(nai); // Let the NetworkAgent know the state of its network + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, (String)msg.obj); nai.asyncChannel.sendMessage( - android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS, + NetworkAgent.CMD_REPORT_NETWORK_STATUS, (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), - 0, null); + 0, redirectUrlBundle); } break; } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index bbb162e7c16f4..d33075634ca04 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -120,8 +120,9 @@ public class NetworkMonitor extends StateMachine { /** * Inform ConnectivityService that the network has been tested. - * obj = NetworkAgentInfo + * obj = String representing URL that Internet probe was redirect to, if it was redirected. * arg1 = One of the NETWORK_TESTED_RESULT_* constants. + * arg2 = NetID. */ public static final int EVENT_NETWORK_TESTED = BASE + 2; @@ -334,8 +335,8 @@ public class NetworkMonitor extends StateMachine { mDontDisplaySigninNotification = true; mUserDoesNotWant = true; mConnectivityServiceHandler.sendMessage(obtainMessage( - EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, 0, - mNetworkAgentInfo)); + EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, + mNetworkAgentInfo.network.netId, null)); // TODO: Should teardown network. mUidResponsibleForReeval = 0; transitionTo(mEvaluatingState); @@ -358,7 +359,7 @@ public class NetworkMonitor extends StateMachine { CaptivePortalStateChangeEvent.logEvent( CaptivePortalStateChangeEvent.NETWORK_MONITOR_VALIDATED); mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, - NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo)); + NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null)); } @Override @@ -412,6 +413,21 @@ public class NetworkMonitor extends StateMachine { } } + /** + * Result of calling isCaptivePortal(). + * @hide + */ + @VisibleForTesting + public static final class CaptivePortalProbeResult { + final int mHttpResponseCode; // HTTP response code returned from Internet probe. + final String mRedirectUrl; // Redirect destination returned from Internet probe. + + public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl) { + mHttpResponseCode = httpResponseCode; + mRedirectUrl = redirectUrl; + } + } + // Being in the EvaluatingState State indicates the Network is being evaluated for internet // connectivity, or that the user has indicated that this network is unwanted. private class EvaluatingState extends State { @@ -464,19 +480,23 @@ public class NetworkMonitor extends StateMachine { // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine // will be unresponsive. isCaptivePortal() could be executed on another Thread // if this is found to cause problems. - int httpResponseCode = isCaptivePortal(); + CaptivePortalProbeResult probeResult = isCaptivePortal(); CaptivePortalCheckResultEvent.logEvent(mNetworkAgentInfo.network.netId, - httpResponseCode); - if (httpResponseCode == 204) { + probeResult.mHttpResponseCode); + if (probeResult.mHttpResponseCode == 204) { transitionTo(mValidatedState); - } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { + } else if (probeResult.mHttpResponseCode >= 200 && + probeResult.mHttpResponseCode <= 399) { + mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, + NETWORK_TEST_RESULT_INVALID, mNetworkAgentInfo.network.netId, + probeResult.mRedirectUrl)); transitionTo(mCaptivePortalState); } else { final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); mConnectivityServiceHandler.sendMessage(obtainMessage( - EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, 0, - mNetworkAgentInfo)); + EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, + mNetworkAgentInfo.network.netId, probeResult.mRedirectUrl)); if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { // Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); @@ -533,8 +553,6 @@ public class NetworkMonitor extends StateMachine { @Override public void enter() { - mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, - NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo)); // Don't annoy user with sign-in notifications. if (mDontDisplaySigninNotification) return; // Create a CustomIntentReceiver that sends us a @@ -639,11 +657,12 @@ public class NetworkMonitor extends StateMachine { * Returns HTTP response code. */ @VisibleForTesting - protected int isCaptivePortal() { - if (!mIsCaptivePortalCheckEnabled) return 204; + protected CaptivePortalProbeResult isCaptivePortal() { + if (!mIsCaptivePortalCheckEnabled) return new CaptivePortalProbeResult(204, null); HttpURLConnection urlConnection = null; int httpResponseCode = 599; + String redirectUrl = null; try { URL url = new URL(getCaptivePortalServerUrl(mContext)); // On networks with a PAC instead of fetching a URL that should result in a 204 @@ -699,6 +718,7 @@ public class NetworkMonitor extends StateMachine { long requestTimestamp = SystemClock.elapsedRealtime(); httpResponseCode = urlConnection.getResponseCode(); + redirectUrl = urlConnection.getHeaderField("location"); // Time how long it takes to get a response to our request long responseTimestamp = SystemClock.elapsedRealtime(); @@ -739,7 +759,7 @@ public class NetworkMonitor extends StateMachine { urlConnection.disconnect(); } } - return httpResponseCode; + return new CaptivePortalProbeResult(httpResponseCode, redirectUrl); } /** diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 2f20a4bf2228c..26eed24c4f39e 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -70,6 +70,7 @@ import android.util.LogPrinter; import com.android.internal.util.WakeupMessage; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; +import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; import com.android.server.net.NetworkPinner; import java.net.InetAddress; @@ -223,11 +224,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final NetworkCapabilities mNetworkCapabilities; private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); + private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; + // Contains the redirectUrl from networkStatus(). Before reading, wait for + // mNetworkStatusReceived. + private String mRedirectUrl; MockNetworkAgent(int transport) { final int type = transportToLegacyType(transport); @@ -266,6 +271,12 @@ public class ConnectivityServiceTest extends AndroidTestCase { public void stopPacketKeepalive(Message msg) { onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } + + @Override + public void networkStatus(int status, String redirectUrl) { + mRedirectUrl = redirectUrl; + mNetworkStatusReceived.open(); + } }; // Waits for the NetworkAgent to be registered, which includes the creation of the // NetworkMonitor. @@ -340,13 +351,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { if (callback != null) mCm.unregisterNetworkCallback(callback); } - public void connectWithCaptivePortal() { + public void connectWithCaptivePortal(String redirectUrl) { mWrappedNetworkMonitor.gen204ProbeResult = 200; + mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl; connect(false); waitFor(new Criteria() { public boolean get() { NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork()); return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} }); mWrappedNetworkMonitor.gen204ProbeResult = 500; + mWrappedNetworkMonitor.gen204ProbeRedirectUrl = null; } public void disconnect() { @@ -381,6 +394,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { public void setExpectedKeepaliveSlot(Integer slot) { mExpectedKeepaliveSlot = slot; } + + public String waitForRedirectUrl() { + assertTrue(mNetworkStatusReceived.block(TIMEOUT_MS)); + return mRedirectUrl; + } } /** @@ -543,6 +561,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private class WrappedNetworkMonitor extends NetworkMonitor { // HTTP response code fed back to NetworkMonitor for Internet connectivity probe. public int gen204ProbeResult = 500; + public String gen204ProbeRedirectUrl = null; public WrappedNetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest) { @@ -550,8 +569,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @Override - protected int isCaptivePortal() { - return gen204ProbeResult; + protected CaptivePortalProbeResult isCaptivePortal() { + return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl); } @Override @@ -1344,8 +1363,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.connectWithCaptivePortal(); + String firstRedirectUrl = "http://example.com/firstPath"; + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); captivePortalCallback.expectCallback(CallbackState.AVAILABLE); + assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); // Take down network. // Expect onLost callback. @@ -1355,8 +1376,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.connectWithCaptivePortal(); + String secondRedirectUrl = "http://example.com/secondPath"; + mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); captivePortalCallback.expectCallback(CallbackState.AVAILABLE); + assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.