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

Commit faac06e3 authored by Automerger Merge Worker's avatar Automerger Merge Worker Committed by Chiachang Wang
Browse files

Use redirect URL to start webview

NetworkMonitor will detect captive portal and may get a redirect
URL from WiFi AP. Redirect URL should able to send to captive
portal app to open the webview instead of detecting again by
captive portal app.

Bug: 134892996
Test: Manually test with captive portal AP
Test: atest NetworkStackTests NetworkStackNextTests
Change-Id: Idf363c79b7243a899121be8a68b32d0541dff14f
Merged-In: Idf363c79b7243a899121be8a68b32d0541dff14f
parent 046a14ee
Loading
Loading
Loading
Loading
+29 −1
Original line number Diff line number Diff line
@@ -141,6 +141,14 @@ public class NetworkStackUtils {
     */
    public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version";

    /**
     * Minimum module version at which to enable dismissal CaptivePortalLogin app in validated
     * network feature. CaptivePortalLogin app will also use validation facilities in
     * {@link NetworkMonitor} to perform portal validation if feature is enabled.
     */
    public static final String DISMISS_PORTAL_IN_VALIDATED_NETWORK =
            "dismiss_portal_in_validated_network";

    static {
        System.loadLibrary("networkstackutilsjni");
    }
@@ -270,12 +278,32 @@ public class NetworkStackUtils {
     */
    public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
            @NonNull String name) {
        final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
                0 /* default value */);
        return isFeatureEnabled(context, namespace, name, false);
    }

    /**
     * Check whether or not one specific experimental feature for a particular namespace from
     * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
     * with current version of property. If this property version is valid, the corresponding
     * experimental feature would be enabled, otherwise disabled.
     * @param context The global context information about an app environment.
     * @param namespace The namespace containing the property to look up.
     * @param name The name of the property to look up.
     * @param defaultEnabled The value to return if the property does not exist or its value is
     *                       null.
     * @return true if this feature is enabled, or false if disabled.
     */
    public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
            @NonNull String name, boolean defaultEnabled) {
        try {
            final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
                    0 /* default value */);
            final long packageVersion = context.getPackageManager().getPackageInfo(
                    context.getPackageName(), 0).getLongVersionCode();
            return (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
            return (propertyVersion == 0 && defaultEnabled)
                    || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Could not find the package name", e);
            return false;
+32 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK;
import static android.net.util.NetworkStackUtils.isEmpty;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;

@@ -960,7 +961,11 @@ public class NetworkMonitor extends StateMachine {
                    final Network network = new Network(mCleartextDnsNetwork);
                    appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
                    final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
                    appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
                    // Use redirect URL from AP if exists.
                    final String portalUrl =
                            (useRedirectUrlForPortal() && probeRes.redirectUrl != null)
                            ? probeRes.redirectUrl : probeRes.detectUrl;
                    appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl);
                    if (probeRes.probeSpec != null) {
                        final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
                        appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
@@ -977,6 +982,15 @@ public class NetworkMonitor extends StateMachine {
            }
        }

        private boolean useRedirectUrlForPortal() {
            // It must match the conditions in CaptivePortalLogin in which the redirect URL is not
            // used to validate that the portal is gone.
            final boolean aboveQ =
                    ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q);
            return aboveQ && mDependencies.isFeatureEnabled(mContext, NAMESPACE_CONNECTIVITY,
                    DISMISS_PORTAL_IN_VALIDATED_NETWORK, aboveQ /* defaultEnabled */);
        }

        @Override
        public void exit() {
            if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
@@ -2385,6 +2399,23 @@ public class NetworkMonitor extends StateMachine {
                    NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
        }

        /**
         * Check whether or not one specific experimental feature for a particular namespace from
         * {@link DeviceConfig} is enabled by comparing NetworkStack module version
         * {@link NetworkStack} with current version of property. If this property version is valid,
         * the corresponding experimental feature would be enabled, otherwise disabled.
         * @param context The global context information about an app environment.
         * @param namespace The namespace containing the property to look up.
         * @param name The name of the property to look up.
         * @param defaultEnabled The value to return if the property does not exist or its value is
         *                       null.
         * @return true if this feature is enabled, or false if disabled.
         */
        public boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
                @NonNull String name, boolean defaultEnabled) {
            return NetworkStackUtils.isFeatureEnabled(context, namespace, name, defaultEnabled);
        }

        public static final Dependencies DEFAULT = new Dependencies();
    }

+56 −1
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK;

import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS;
import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS;
@@ -59,6 +60,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -478,6 +480,7 @@ public class NetworkMonitorTest {

        mCreatedNetworkMonitors = new HashSet<>();
        mRegisteredReceivers = new HashSet<>();
        setDismissPortalInValidatedNetwork(false);
    }

    @After
@@ -1087,7 +1090,7 @@ public class NetworkMonitorTest {
    public void testLaunchCaptivePortalApp() throws Exception {
        setSslException(mHttpsConnection);
        setPortal302(mHttpConnection);

        when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
        final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
        notifyNetworkConnected(nm, METERED_CAPABILITIES);

@@ -1111,6 +1114,9 @@ public class NetworkMonitorTest {
        // framework and only intended for the captive portal app, but the framework needs
        // the network to identify the right NetworkMonitor.
        assertEquals(TEST_NETID, networkCaptor.getValue().netId);
        // Portal URL should be detection URL.
        final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
        assertEquals(TEST_HTTP_URL, redirectUrl);

        // Have the app report that the captive portal is dismissed, and check that we revalidate.
        setStatus(mHttpsConnection, 204);
@@ -1442,6 +1448,50 @@ public class NetworkMonitorTest {
                .notifyNetworkTested(eq(VALIDATION_RESULT_VALID), any());
    }

    @Test
    public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception {
        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
        testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL);
    }

    @Test
    public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception {
        assumeFalse(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
        testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL);
    }

    private void testDismissPortalInValidatedNetworkEnabled(String portalUrl) throws Exception {
        setDismissPortalInValidatedNetwork(true);
        setSslException(mHttpsConnection);
        setPortal302(mHttpConnection);
        when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
        final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
        notifyNetworkConnected(nm, METERED_CAPABILITIES);

        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
            .showProvisioningNotification(any(), any());

        assertEquals(1, mRegisteredReceivers.size());
        // Check that startCaptivePortalApp sends the expected intent.
        nm.launchCaptivePortalApp();

        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
        final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
        verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
            .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
        verify(mNotifier).notifyCaptivePortalValidationPending(networkCaptor.getValue());
        final Bundle bundle = bundleCaptor.getValue();
        final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
        assertEquals(TEST_NETID, bundleNetwork.netId);
        // Network is passed both in bundle and as parameter, as the bundle is opaque to the
        // framework and only intended for the captive portal app, but the framework needs
        // the network to identify the right NetworkMonitor.
        assertEquals(TEST_NETID, networkCaptor.getValue().netId);
        // Portal URL should be redirect URL.
        final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
        assertEquals(portalUrl, redirectUrl);
    }

    @Test
    public void testEvaluationState_clearProbeResults() throws Exception {
        final NetworkMonitor nm = runValidatedNetworkTest();
@@ -1602,6 +1652,11 @@ public class NetworkMonitorTest {
                eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
    }

    private void setDismissPortalInValidatedNetwork(boolean enabled) {
        when(mDependencies.isFeatureEnabled(any(), any(),
                eq(DISMISS_PORTAL_IN_VALIDATED_NETWORK), anyBoolean())).thenReturn(enabled);
    }

    private void runPortalNetworkTest(int result) {
        runNetworkTest(result);
        assertEquals(1, mRegisteredReceivers.size());