Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4bc78eba authored by Calvin On's avatar Calvin On
Browse files

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 be96da11)
parent f9ca194d
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -7991,12 +7991,46 @@ 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";

+15 −1
Original line number Diff line number Diff line
@@ -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:
+9 −3
Original line number Diff line number Diff line
@@ -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;

+60 −5
Original line number Diff line number Diff line
@@ -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);