From 4bc78eba6f16ef84206aaed9edd5ca4bb4f6c420 Mon Sep 17 00:00:00 2001 From: Calvin On Date: Tue, 11 Oct 2016 15:10:46 -0700 Subject: [PATCH] Add option to skip and avoid captive portals. Test: ConnectivityServiceTest updated with test cases. Test: Manually tested against att-wifi in B42. Bug: 30222699 Change-Id: Ibe63942da04748ab0406e24e0f44be31d47710a0 (cherry picked from commit be96da11ccb5dd500f920c3ba90d350857293b3d) --- core/java/android/provider/Settings.java | 34 +++++++++++ .../com/android/server/ConnectivityService.java | 16 +++++- .../server/connectivity/NetworkMonitor.java | 12 +++- .../android/server/ConnectivityServiceTest.java | 65 ++++++++++++++++++++-- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3b5bd52e580e..496a82f6f20e 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7992,11 +7992,45 @@ public final class Settings { public static final String PAC_CHANGE_DELAY = "pac_change_delay"; /** + * Don't attempt to detect captive portals. + * + * @hide + */ + public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; + + /** + * When detecting a captive portal, display a notification that + * prompts the user to sign in. + * + * @hide + */ + public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; + + /** + * When detecting a captive portal, immediately disconnect from the + * network and do not reconnect to that network in the future. + * + * @hide + */ + public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; + + /** + * What to do when connecting a network that presents a captive portal. + * Must be one of the CAPTIVE_PORTAL_MODE_* constants above. + * + * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT. + * @hide + */ + public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode"; + + /** * Setting to turn off captive portal detection. Feature is enabled by * default and the setting needs to be set to 0 to disable it. * + * @deprecated use CAPTIVE_PORTAL_MODE_IGNORE to disable captive portal detection * @hide */ + @Deprecated public static final String CAPTIVE_PORTAL_DETECTION_ENABLED = "captive_portal_detection_enabled"; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 5f59e32ebb6b..614a94fa1e20 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2261,11 +2261,19 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mNetworkForNetId) { nai = mNetworkForNetId.get(netId); } - // If captive portal status has changed, update capabilities. + // If captive portal status has changed, update capabilities or disconnect. if (nai != null && (visible != nai.lastCaptivePortalDetected)) { final int oldScore = nai.getCurrentScore(); nai.lastCaptivePortalDetected = visible; nai.everCaptivePortalDetected |= visible; + if (nai.lastCaptivePortalDetected && + Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) { + if (DBG) log("Avoiding captive portal network: " + nai.name()); + nai.asyncChannel.sendMessage( + NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + teardownUnneededNetwork(nai); + break; + } updateCapabilities(oldScore, nai, nai.networkCapabilities); } if (!visible) { @@ -2286,6 +2294,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + private int getCaptivePortalMode() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_MODE, + Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); + } + private boolean maybeHandleNetworkAgentInfoMessage(Message msg) { switch (msg.what) { default: diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 6eb89facca76..c73d1dd95437 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -211,7 +211,9 @@ public class NetworkMonitor extends StateMachine { private final NetworkRequest mDefaultRequest; private final IpConnectivityLog mMetricsLog; - private boolean mIsCaptivePortalCheckEnabled; + @VisibleForTesting + protected boolean mIsCaptivePortalCheckEnabled; + private boolean mUseHttps; // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. @@ -265,7 +267,8 @@ public class NetworkMonitor extends StateMachine { setInitialState(mDefaultState); mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT) + != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE; mUseHttps = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; @@ -632,7 +635,10 @@ public class NetworkMonitor extends StateMachine { @VisibleForTesting protected CaptivePortalProbeResult isCaptivePortal() { - if (!mIsCaptivePortalCheckEnabled) return new CaptivePortalProbeResult(204); + if (!mIsCaptivePortalCheckEnabled) { + validationLog("Validation disabled."); + return new CaptivePortalProbeResult(204); + } URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null; diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 4af1cf1a0df8..65f9399c5a80 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -236,6 +236,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); + private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; @@ -291,6 +292,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { mRedirectUrl = redirectUrl; mNetworkStatusReceived.open(); } + + @Override + protected void preventAutomaticReconnect() { + mPreventReconnectReceived.open(); + } }; // Waits for the NetworkAgent to be registered, which includes the creation of the // NetworkMonitor. @@ -375,11 +381,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { 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() { @@ -391,6 +392,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { return new Network(mNetworkAgent.netId); } + public ConditionVariable getPreventReconnectReceived() { + return mPreventReconnectReceived; + } + public ConditionVariable getDisconnectedCV() { return mDisconnected; } @@ -597,6 +602,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { @Override protected CaptivePortalProbeResult isCaptivePortal() { + if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); } return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null); } } @@ -743,6 +749,9 @@ public class ConnectivityServiceTest extends AndroidTestCase { mService.systemReady(); mCm = new WrappedConnectivityManager(getContext(), mService); mCm.bindProcessToNetwork(null); + + // Ensure that the default setting for Captive Portals is used for most tests + setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); } public void tearDown() throws Exception { @@ -1704,6 +1713,47 @@ public class ConnectivityServiceTest extends AndroidTestCase { validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); } + @LargeTest + public void testAvoidOrIgnoreCaptivePortals() { + 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); + + setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID); + // Bring up a network with a captive portal. + // Expect it to fail to connect and not result in any callbacks. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + String firstRedirectUrl = "http://example.com/firstPath"; + + ConditionVariable disconnectCv = mWiFiNetworkAgent.getDisconnectedCV(); + ConditionVariable avoidCv = mWiFiNetworkAgent.getPreventReconnectReceived(); + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); + waitFor(disconnectCv); + waitFor(avoidCv); + + assertNoCallbacks(captivePortalCallback, validatedCallback); + + // Now test ignore mode. + setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE); + + // Bring up a network with a captive portal. + // Since we're ignoring captive portals, the network will validate. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + String secondRedirectUrl = "http://example.com/secondPath"; + mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); + + // Expect NET_CAPABILITY_VALIDATED onAvailable callback. + validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + // But there should be no CaptivePortal callback. + captivePortalCallback.assertNoCallback(); + } + @SmallTest public void testInvalidNetworkSpecifier() { boolean execptionCalled = true; @@ -1844,6 +1894,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { mCm.unregisterNetworkCallback(cellNetworkCallback); } + private void setCaptivePortalMode(int mode) { + ContentResolver cr = mServiceContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode); + } + private void setMobileDataAlwaysOn(boolean enable) { ContentResolver cr = mServiceContext.getContentResolver(); Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0); -- 2.11.0