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

Commit d9ba175a authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 6402594 from 672ebae0 to sc-release

Change-Id: I1b54c4b3c3a25ad47c1e2ee607136b3e49d57f68
parents 24a67b66 672ebae0
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -66,22 +66,23 @@ public class NduseroptMessage extends NetlinkMessage {
        super(header);

        // The structure itself.
        buf.order(ByteOrder.nativeOrder());
        buf.order(ByteOrder.nativeOrder());  // Restored in the finally clause inside parse().
        final int start = buf.position();
        family = buf.get();
        buf.get();  // Skip 1 byte of padding.
        opts_len = Short.toUnsignedInt(buf.getShort());
        ifindex = buf.getInt();
        icmp_type = buf.get();
        icmp_code = buf.get();
        buf.order(ByteOrder.BIG_ENDIAN);
        buf.position(buf.position() + 6);  // Skip 6 bytes of padding.

        // The ND option.
        // Ensure we don't read past opts_len even if the option length is invalid.
        // Note that this check is not really necessary since if the option length is not valid,
        // this struct won't be very useful to the caller.
        buf.order(ByteOrder.BIG_ENDIAN);
        int oldLimit = buf.limit();
        buf.limit(STRUCT_SIZE + opts_len);
        buf.limit(start + STRUCT_SIZE + opts_len);
        try {
            option = NdOption.parse(buf);
        } finally {
@@ -89,7 +90,7 @@ public class NduseroptMessage extends NetlinkMessage {
        }

        // The source address.
        int newPosition = STRUCT_SIZE + opts_len;
        int newPosition = start + STRUCT_SIZE + opts_len;
        if (newPosition >= buf.limit()) {
            throw new IllegalArgumentException("ND options extend past end of buffer");
        }
@@ -118,6 +119,7 @@ public class NduseroptMessage extends NetlinkMessage {
     */
    public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) {
        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
        ByteOrder oldOrder = buf.order();
        try {
            return new NduseroptMessage(header, buf);
        } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) {
@@ -125,6 +127,8 @@ public class NduseroptMessage extends NetlinkMessage {
            // Convention in this package is that null indicates that the option was truncated, so
            // callers must already handle it.
            return null;
        } finally {
            buf.order(oldOrder);
        }
    }

+6 −0
Original line number Diff line number Diff line
@@ -233,6 +233,9 @@ public class DhcpClient extends StateMachine {
    public static final int CMD_START_PRECONNECTION         = PUBLIC_BASE + 10;
    public static final int CMD_ABORT_PRECONNECTION         = PUBLIC_BASE + 11;

    // Command to rebind the leased IPv4 address on L2 roaming happened.
    public static final int CMD_REFRESH_LINKADDRESS         = PUBLIC_BASE + 12;

    /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
    public static final int DHCP_SUCCESS = 1;
    public static final int DHCP_FAILURE = 2;
@@ -1674,6 +1677,9 @@ public class DhcpClient extends StateMachine {
                case CMD_RENEW_DHCP:
                    preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState);
                    return HANDLED;
                case CMD_REFRESH_LINKADDRESS:
                    transitionTo(mDhcpRebindingState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
+2 −1
Original line number Diff line number Diff line
@@ -335,7 +335,8 @@ public abstract class DhcpPacket {
     * proposed by the client (from an earlier DHCP negotiation) or
     * supplied by the server.
     */
    protected final Inet4Address mClientIp;
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public final Inet4Address mClientIp;
    protected final Inet4Address mYourIp;
    private final Inet4Address mNextIp;
    protected final Inet4Address mRelayIp;
+68 −4
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.net.Layer2InformationParcelable;
import android.net.Layer2PacketParcelable;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.NattKeepalivePacketDataParcelable;
import android.net.NetworkStackIpMemoryStore;
import android.net.ProvisioningConfigurationParcelable;
@@ -92,6 +93,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -381,7 +383,8 @@ public class IpClient extends StateMachine {
    private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
    private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
    private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15;
    protected static final int CMD_COMPLETE_PRECONNECTION = 16;
    private static final int CMD_COMPLETE_PRECONNECTION = 16;
    private static final int CMD_UPDATE_L2INFORMATION = 17;

    // Internal commands to use instead of trying to call transitionTo() inside
    // a given State's enter() method. Calling transitionTo() from enter/exit
@@ -423,6 +426,14 @@ public class IpClient extends StateMachine {
                    new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 }
    ));

    // Initialize configurable particular SSID set supporting DHCP Roaming feature. See
    // b/131797393 for more details.
    private static final Set<String> DHCP_ROAMING_SSID_SET = new HashSet<>(
            Arrays.asList(
                    "0001docomo", "ollehWiFi", "olleh GiGa WiFi", "KT WiFi",
                    "KT GiGA WiFi", "marente"
    ));

    private final State mStoppedState = new StoppedState();
    private final State mStoppingState = new StoppingState();
    private final State mClearingIpAddressesState = new ClearingIpAddressesState();
@@ -470,6 +481,7 @@ public class IpClient extends StateMachine {
    private String mGroupHint; // The group hint for this network, for writing into the memory store
    private boolean mMulticastFiltering;
    private long mStartTimeMillis;
    private MacAddress mCurrentBssid;

    /**
     * Reading the snapshot is an asynchronous operation initiated by invoking
@@ -704,7 +716,6 @@ public class IpClient extends StateMachine {
            enforceNetworkStackCallingPermission();
            IpClient.this.notifyPreconnectionComplete(success);
        }

        @Override
        public void updateLayer2Information(Layer2InformationParcelable info) {
            enforceNetworkStackCallingPermission();
@@ -772,6 +783,16 @@ public class IpClient extends StateMachine {
            return;
        }

        final ScanResultInfo scanResultInfo = req.mScanResultInfo;
        mCurrentBssid = null;
        if (scanResultInfo != null) {
            try {
                mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
            } catch (IllegalArgumentException e) {
                Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
                        + " in provisioning configuration", e);
            }
        }
        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
    }

@@ -819,10 +840,15 @@ public class IpClient extends StateMachine {

    /**
     * Set the L2 key and group hint for storing info into the memory store.
     *
     * This method is only supported on Q devices. For R or above releases,
     * caller should call #updateLayer2Information() instead.
     */
    public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
            sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint));
        }
    }

    /**
     * Set the HTTP Proxy configuration to use.
@@ -880,7 +906,7 @@ public class IpClient extends StateMachine {
     * Update the network bssid, L2Key and GroupHint layer2 information.
     */
    public void updateLayer2Information(@NonNull Layer2InformationParcelable info) {
        // TODO: add specific implementation.
        sendMessage(CMD_UPDATE_L2INFORMATION, info);
    }

    /**
@@ -1502,6 +1528,36 @@ public class IpClient extends StateMachine {
        }
    }

    private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) {
        mL2Key = info.l2Key;
        mGroupHint = info.groupHint;

        // This means IpClient is still in the StoppedState, WiFi is trying to associate
        // to the AP, just update L2Key and GroupHint at this stage, because these members
        // will be used when starting DhcpClient.
        if (info.bssid == null || mCurrentBssid == null) return;

        // If the BSSID has not changed, there is nothing to do.
        if (info.bssid.equals(mCurrentBssid)) return;

        if (mIpReachabilityMonitor != null) {
            mIpReachabilityMonitor.probeAll();
        }

        // Check whether to refresh previous IP lease on L2 roaming happened.
        final String ssid = removeDoubleQuotes(mConfiguration.mDisplayName);
        if (DHCP_ROAMING_SSID_SET.contains(ssid) && mDhcpClient != null) {
            if (DBG) {
                Log.d(mTag, "L2 roaming happened from " + mCurrentBssid
                        + " to " + info.bssid
                        + " , SSID: " + ssid
                        + " , starting refresh leased IP address");
            }
            mDhcpClient.sendMessage(DhcpClient.CMD_REFRESH_LINKADDRESS);
        }
        mCurrentBssid = info.bssid;
    }

    class StoppedState extends State {
        @Override
        public void enter() {
@@ -1554,6 +1610,10 @@ public class IpClient extends StateMachine {
                    break;
                }

                case CMD_UPDATE_L2INFORMATION:
                    handleUpdateL2Information((Layer2InformationParcelable) msg.obj);
                    break;

                case CMD_SET_MULTICAST_FILTER:
                    mMulticastFiltering = (boolean) msg.obj;
                    break;
@@ -1763,6 +1823,10 @@ public class IpClient extends StateMachine {
                    break;
                }

                case CMD_UPDATE_L2INFORMATION:
                    handleUpdateL2Information((Layer2InformationParcelable) msg.obj);
                    break;

                case EVENT_PROVISIONING_TIMEOUT:
                    handleProvisioningFailure();
                    break;
+187 −13
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;

import androidx.annotation.ArrayRes;
import androidx.annotation.BoolRes;
@@ -192,7 +193,13 @@ import java.util.Objects;
import java.util.Random;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.CompletionService;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
@@ -218,6 +225,8 @@ public class NetworkMonitor extends StateMachine {
    private static final int SOCKET_TIMEOUT_MS = 10000;
    private static final int PROBE_TIMEOUT_MS  = 3000;

    private static final int UNSET_MCC_OR_MNC = -1;

    private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096;
    private static final String ACCEPT_HEADER = "Accept";
    private static final String CONTENT_TYPE_HEADER = "Content-Type";
@@ -241,6 +250,24 @@ public class NetworkMonitor extends StateMachine {
        }
    }

    @VisibleForTesting
    protected static final class MccMncOverrideInfo {
        public final int mcc;
        public final int mnc;
        MccMncOverrideInfo(int mcc, int mnc) {
            this.mcc = mcc;
            this.mnc = mnc;
        }
    }

    @VisibleForTesting
    protected static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>();

    static {
        // CTC
        sCarrierIdToMccMnc.put(1854, new MccMncOverrideInfo(460, 03));
    }

    /**
     * ConnectivityService has sent a notification to indicate that network has connected.
     * Initiates Network Validation.
@@ -351,6 +378,10 @@ public class NetworkMonitor extends StateMachine {
    // 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;
    // Max thread pool size for parallel probing. Fixed thread pool size to control the thread
    // number used for either HTTP or HTTPS probing.
    @VisibleForTesting
    static final int MAX_PROBE_THREAD_POOL_SIZE = 5;
    private String mPrivateDnsProviderHostname = "";

    private final Context mContext;
@@ -1507,25 +1538,51 @@ public class NetworkMonitor extends StateMachine {
        }
    }

    /**
     * Return a matched MccMncOverrideInfo if carrier id and sim mccmnc are matching a record in
     * sCarrierIdToMccMnc.
     */
    @VisibleForTesting
    protected Context getContextByMccIfNoSimCardOrDefault() {
    @Nullable
    MccMncOverrideInfo getMccMncOverrideInfo() {
        final int carrierId = mTelephonyManager.getSimCarrierId();
        return sCarrierIdToMccMnc.get(carrierId);
    }

    private Context getContextByMccMnc(final int mcc, final int mnc) {
        final Configuration config = mContext.getResources().getConfiguration();
        if (mcc != UNSET_MCC_OR_MNC) config.mcc = mcc;
        if (mnc != UNSET_MCC_OR_MNC) config.mnc = mnc;
        return mContext.createConfigurationContext(config);
    }

    @VisibleForTesting
    protected Context getCustomizedContextOrDefault() {
        // Return customized context if carrier id can match a record in sCarrierIdToMccMnc.
        final MccMncOverrideInfo overrideInfo = getMccMncOverrideInfo();
        if (overrideInfo != null) {
            return getContextByMccMnc(overrideInfo.mcc, overrideInfo.mnc);
        }

        // Use neighbor mcc feature only works when the config_no_sim_card_uses_neighbor_mcc is
        // true and there is no sim card inserted.
        final boolean useNeighborResource =
                getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc, false);
        if (!useNeighborResource
                || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) {
            return mContext;
        }

        final String mcc = getLocationMcc();
        if (TextUtils.isEmpty(mcc)) {
            return mContext;
        }
        final Configuration config = mContext.getResources().getConfiguration();
        config.mcc = Integer.parseInt(mcc);
        return mContext.createConfigurationContext(config);

        return getContextByMccMnc(Integer.parseInt(mcc), UNSET_MCC_OR_MNC);
    }

    private String getCaptivePortalServerHttpsUrl() {
        final Context targetContext = getContextByMccIfNoSimCardOrDefault();
        final Context targetContext = getCustomizedContextOrDefault();
        return getSettingFromResource(targetContext, R.string.config_captive_portal_https_url,
                R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
    }
@@ -1604,7 +1661,7 @@ public class NetworkMonitor extends StateMachine {
     * on one URL that can be used, while NetworkMonitor may implement more complex logic.
     */
    public String getCaptivePortalServerHttpUrl() {
        final Context targetContext = getContextByMccIfNoSimCardOrDefault();
        final Context targetContext = getCustomizedContextOrDefault();
        return getSettingFromResource(targetContext, R.string.config_captive_portal_http_url,
                R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
    }
@@ -1759,7 +1816,7 @@ public class NetworkMonitor extends StateMachine {
     */
    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
            @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
        final Resources res = getContextByMccIfNoSimCardOrDefault().getResources();
        final Resources res = getCustomizedContextOrDefault().getResources();
        return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId),
                resourceConverter);
    }
@@ -1777,7 +1834,7 @@ public class NetworkMonitor extends StateMachine {
     */
    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
            String[] defaultConfig, @NonNull Function<String, T> resourceConverter) {
        final Resources res = getContextByMccIfNoSimCardOrDefault().getResources();
        final Resources res = getCustomizedContextOrDefault().getResources();
        String[] configValue = res.getStringArray(configResId);

        if (configValue.length == 0) {
@@ -1885,9 +1942,13 @@ public class NetworkMonitor extends StateMachine {
        if (pacUrl != null) {
            result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
        } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
            // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
            result = sendHttpAndHttpsParallelWithFallbackProbes(
                    proxyInfo, httpsUrls[0], httpUrls[0]);
        } else if (mUseHttps) {
            // Probe results are reported inside sendParallelHttpProbes.
            result = sendParallelHttpProbes(proxyInfo, httpsUrls[0], httpUrls[0]);
            // Support result aggregation from multiple Urls.
            result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls);
        } else {
            result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP);
            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
@@ -2279,7 +2340,9 @@ public class NetworkMonitor extends StateMachine {
            if (capportData != null && capportData.isCaptive()) {
                if (capportData.getUserPortalUrl() == null) {
                    validationLog("Missing user-portal-url from capport response");
                    return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP);
                    return new CapportApiProbeResult(
                            sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP),
                            capportData);
                }
                final String loginUrlString = capportData.getUserPortalUrl().toString();
                // Starting from R (where CaptivePortalData was introduced), the captive portal app
@@ -2299,7 +2362,7 @@ public class NetworkMonitor extends StateMachine {
            // redirect when there is one.
            final CaptivePortalProbeResult res =
                    sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP);
            return capportData == null ? res : new CapportApiProbeResult(res, capportData);
            return mCaptivePortalApiUrl == null ? res : new CapportApiProbeResult(res, capportData);
        }
    }

@@ -2316,6 +2379,117 @@ public class NetworkMonitor extends StateMachine {
                        && captivePortalApiUrl == null);
    }

    private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy,
            @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) {
        // If multiple URLs are required to ensure the correctness of validation, send parallel
        // probes to explore the result in separate probe threads and aggregate those results into
        // one as the final result for either HTTP or HTTPS.

        // Number of probes to wait for.
        final int num = httpsUrls.length + httpUrls.length;
        // Fixed pool to prevent configuring too many urls to exhaust system resource.
        final ExecutorService executor = Executors.newFixedThreadPool(
                Math.min(num, MAX_PROBE_THREAD_POOL_SIZE));
        final CompletionService<CaptivePortalProbeResult> ecs =
                new ExecutorCompletionService<CaptivePortalProbeResult>(executor);
        final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
        final List<Future<CaptivePortalProbeResult>> futures = new ArrayList<>();

        try {
            // Queue https and http probe.

            // Each of these HTTP probes will start with probing capport API if present. So if
            // multiple HTTP URLs are configured, AP will send multiple identical accesses to the
            // capport URL. Thus, send capport API probing with one of the HTTP probe is enough.
            // Probe capport API with the first HTTP probe.
            // TODO: Have the capport probe as a different probe for cleanliness.
            final URL urlMaybeWithCapport = httpUrls[0];
            for (final URL url : httpUrls) {
                futures.add(ecs.submit(() -> new HttpProbe(proxy, url,
                        url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe()));
            }

            for (final URL url : httpsUrls) {
                futures.add(
                        ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe()));
            }

            final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>();
            for (int i = 0; i < num; i++) {
                completedProbes.add(ecs.take().get());
                final CaptivePortalProbeResult res = evaluateCapportResult(
                        completedProbes, httpsUrls.length, capportApiUrl != null /* hasCapport */);
                if (res != null) {
                    reportProbeResult(res);
                    return res;
                }
            }
        } catch (ExecutionException e) {
            Log.e(TAG, "Error sending probes.", e);
        } catch (InterruptedException e) {
            // Ignore interrupted probe result because result is not important to conclude the
            // result.
        } finally {
            // Interrupt ongoing probes since we have already gotten result from one of them.
            futures.forEach(future -> future.cancel(true));
            executor.shutdownNow();
        }

        return CaptivePortalProbeResult.failed(ValidationProbeEvent.PROBE_HTTPS);
    }

    @Nullable
    private CaptivePortalProbeResult evaluateCapportResult(
            List<CaptivePortalProbeResult> probes, int numHttps, boolean hasCapport) {
        CaptivePortalProbeResult capportResult = null;
        CaptivePortalProbeResult httpPortalResult = null;
        int httpSuccesses = 0;
        int httpsSuccesses = 0;
        int httpsFailures = 0;

        for (CaptivePortalProbeResult probe : probes) {
            if (probe instanceof CapportApiProbeResult) {
                capportResult = probe;
            } else if (probe.isConcludedFromHttps()) {
                if (probe.isSuccessful()) httpsSuccesses++;
                else httpsFailures++;
            } else { // http probes
                if (probe.isPortal()) {
                    // Unlike https probe, http probe may have redirect url information kept in the
                    // probe result. Thus, the result can not be newly created with response code
                    // only. If the captive portal behavior will be varied because of different
                    // probe URLs, this means that if the portal returns different redirect URLs for
                    // different probes and has a different behavior depending on the URL, then the
                    // behavior of the login page may differ depending on the order in which the
                    // probes terminate. However, NetworkMonitor does have to choose one of the
                    // redirect URLs and right now there is no clue at all which of the probe has
                    // the better redirect URL, so there is no telling which is best to use.
                    // Therefore the current code just uses whichever happens to be the last one to
                    // complete.
                    httpPortalResult = probe;
                } else if (probe.isSuccessful()) {
                    httpSuccesses++;
                }
            }
        }
        // If there is Capport url configured but the result is not available yet, wait for it.
        if (hasCapport && capportResult == null) return null;
        // Capport API saying it's a portal is authoritative.
        if (capportResult != null && capportResult.isPortal()) return capportResult;
        // Any HTTP probes saying probe portal is conclusive.
        if (httpPortalResult != null) return httpPortalResult;
        // Any HTTPS probes works then the network validates.
        if (httpsSuccesses > 0) {
            return CaptivePortalProbeResult.success(1 << ValidationProbeEvent.PROBE_HTTPS);
        }
        // All HTTPS failed and at least one HTTP succeeded, then it's partial.
        if (httpsFailures == numHttps && httpSuccesses > 0) {
            return CaptivePortalProbeResult.PARTIAL;
        }
        // Otherwise, the result is unknown yet.
        return null;
    }

    private void reportProbeResult(@NonNull CaptivePortalProbeResult res) {
        if (res instanceof CapportApiProbeResult) {
            maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData());
@@ -2332,7 +2506,7 @@ public class NetworkMonitor extends StateMachine {
        }
    }

    private CaptivePortalProbeResult sendParallelHttpProbes(
    private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
            ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
        // Number of probes to wait for. If a probe completes with a conclusive answer
        // it shortcuts the latch immediately by forcing the count to 0.
Loading