Loading apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java +10 −0 Original line number Original line Diff line number Diff line Loading @@ -92,6 +92,16 @@ public class CaptivePortalDataShimImpl return mData.isCaptive(); return mData.isCaptive(); } } @Override public long getByteLimit() { return mData.getByteLimit(); } @Override public long getExpiryTimeMillis() { return mData.getExpiryTimeMillis(); } @Override @Override public Uri getUserPortalUrl() { public Uri getUserPortalUrl() { return mData.getUserPortalUrl(); return mData.getUserPortalUrl(); Loading apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java +10 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,16 @@ public interface CaptivePortalDataShim { */ */ boolean isCaptive(); boolean isCaptive(); /** * @see android.net.CaptivePortalData#getByteLimit() */ long getByteLimit(); /** * @see android.net.CaptivePortalData#getExpiryTimeMillis() */ long getExpiryTimeMillis(); /** /** * @see android.net.CaptivePortalData#getUserPortalUrl() * @see android.net.CaptivePortalData#getUserPortalUrl() */ */ Loading common/networkstackclient/Android.bp +4 −2 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ aidl_interface { apex_available: [ apex_available: [ "//apex_available:platform", "//apex_available:platform", "com.android.wifi", "com.android.wifi", "com.android.bluetooth.updatable", ], ], // this is part of updatable modules(NetworkStack) which targets 29(Q) // this is part of updatable modules(NetworkStack) which targets 29(Q) min_sdk_version: "29", min_sdk_version: "29", Loading Loading @@ -92,6 +93,7 @@ aidl_interface { "//apex_available:platform", "//apex_available:platform", "com.android.bluetooth.updatable", "com.android.bluetooth.updatable", "com.android.wifi", "com.android.wifi", "com.android.tethering", ], ], // this is part of updatable modules(NetworkStack) which targets 29(Q) // this is part of updatable modules(NetworkStack) which targets 29(Q) min_sdk_version: "29", min_sdk_version: "29", Loading Loading @@ -133,8 +135,8 @@ java_library { "src/android/net/shared/**/*.java", "src/android/net/shared/**/*.java", ], ], static_libs: [ static_libs: [ "ipmemorystore-aidl-interfaces-java", "ipmemorystore-aidl-interfaces-unstable-java", "networkstack-aidl-interfaces-java", "networkstack-aidl-interfaces-unstable-java", ], ], visibility: [ visibility: [ "//frameworks/base/packages/Tethering", "//frameworks/base/packages/Tethering", Loading src/com/android/networkstack/metrics/NetworkValidationMetrics.java 0 → 100644 +220 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.networkstack.metrics; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static java.lang.System.currentTimeMillis; import android.net.INetworkMonitor; import android.net.NetworkCapabilities; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.ValidationProbeEvent; import android.net.util.NetworkStackUtils; import android.net.util.Stopwatch; import android.stats.connectivity.ProbeResult; import android.stats.connectivity.ProbeType; import android.stats.connectivity.TransportType; import android.stats.connectivity.ValidationResult; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.networkstack.apishim.common.CaptivePortalDataShim; /** * Class to record the network validation into statsd. * 1. Fill in NetworkValidationReported proto. * 2. Write the NetworkValidationReported proto into statsd. * @hide */ public class NetworkValidationMetrics { private final NetworkValidationReported.Builder mStatsBuilder = NetworkValidationReported.newBuilder(); private final ProbeEvents.Builder mProbeEventsBuilder = ProbeEvents.newBuilder(); private final CapportApiData.Builder mCapportApiDataBuilder = CapportApiData.newBuilder(); private final Stopwatch mWatch = new Stopwatch(); private int mValidationIndex = 0; // Define a maximum size that can store events. public static final int MAX_PROBE_EVENTS_COUNT = 20; /** * Reset this NetworkValidationMetrics. */ public void reset(@Nullable NetworkCapabilities nc) { mStatsBuilder.clear(); mProbeEventsBuilder.clear(); mCapportApiDataBuilder.clear(); mWatch.restart(); mStatsBuilder.setTransportType(getTransportTypeFromNC(nc)); mValidationIndex++; } /** * Returns the enum TransportType * * @param NetworkCapabilities * @return the TransportType which is defined in * core/proto/android/stats/connectivity/network_stack.proto */ @VisibleForTesting public static TransportType getTransportTypeFromNC( @Nullable NetworkCapabilities nc) { if (nc == null) return TransportType.TT_UNKNOWN; boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET); boolean hasVpn = nc.hasTransport(TRANSPORT_VPN); boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN; if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI; if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR; if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH; if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasLopan) return TransportType.TT_LOWPAN; return TransportType.TT_UNKNOWN; } /** * Map {@link ValidationProbeEvent} to {@link ProbeType}. */ public static ProbeType probeTypeToEnum(final int probeType) { switch(probeType) { case ValidationProbeEvent.PROBE_DNS: return ProbeType.PT_DNS; case ValidationProbeEvent.PROBE_HTTP: return ProbeType.PT_HTTP; case ValidationProbeEvent.PROBE_HTTPS: return ProbeType.PT_HTTPS; case ValidationProbeEvent.PROBE_PAC: return ProbeType.PT_PAC; case ValidationProbeEvent.PROBE_FALLBACK: return ProbeType.PT_FALLBACK; case ValidationProbeEvent.PROBE_PRIVDNS: return ProbeType.PT_PRIVDNS; default: return ProbeType.PT_UNKNOWN; } } /** * Map {@link CaptivePortalProbeResult} to {@link ProbeResult}. */ public static ProbeResult httpProbeResultToEnum(final CaptivePortalProbeResult result) { if (result == null) return ProbeResult.PR_UNKNOWN; if (result.isSuccessful()) { return ProbeResult.PR_SUCCESS; } else if (result.isDnsPrivateIpResponse()) { return ProbeResult.PR_PRIVATE_IP_DNS; } else if (result.isFailed()) { return ProbeResult.PR_FAILURE; } else if (result.isPortal()) { return ProbeResult.PR_PORTAL; } else { return ProbeResult.PR_UNKNOWN; } } /** * Map validation result (as per INetworkMonitor) to {@link ValidationResult}. */ @VisibleForTesting public static ValidationResult validationResultToEnum(int result, String redirectUrl) { if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { return ValidationResult.VR_SUCCESS; } else if (redirectUrl != null) { return ValidationResult.VR_PORTAL; } else if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL) != 0) { return ValidationResult.VR_PARTIAL; } else { return ValidationResult.VR_FAILURE; } } /** * Write each network probe event to mProbeEventsBuilder. */ public void setProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, @Nullable final CaptivePortalDataShim capportData) { // When the number of ProbeEvents of mProbeEventsBuilder exceeds // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; int latencyUs = NetworkStackUtils.saturatedCast(durationUs); final ProbeEvent.Builder probeEventBuilder = ProbeEvent.newBuilder() .setLatencyMicros(latencyUs) .setProbeType(type) .setProbeResult(result); if (capportData != null) { final long secondsRemaining = (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; mCapportApiDataBuilder .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit())) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); } mProbeEventsBuilder.addProbeEvent(probeEventBuilder); } /** * Write the network validation info to mStatsBuilder. */ public void setValidationResult(int result, String redirectUrl) { mStatsBuilder.setValidationResult(validationResultToEnum(result, redirectUrl)); } /** * Write the NetworkValidationReported proto to statsd. */ public NetworkValidationReported sendValidationStats() { if (!mWatch.isStarted()) return null; mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setValidationIndex(mValidationIndex); // write a random value(0 ~ 999) for sampling. mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); final NetworkValidationReported mStats = mStatsBuilder.build(); final byte[] probeEvents = mStats.getProbeEvents().toByteArray(); NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, mStats.getTransportType().getNumber(), probeEvents, mStats.getValidationResult().getNumber(), mStats.getLatencyMicros(), mStats.getValidationIndex(), mStats.getRandomNumber()); mWatch.reset(); return mStats; } } src/com/android/server/connectivity/NetworkMonitor.java +82 −12 Original line number Original line Diff line number Diff line Loading @@ -129,6 +129,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings; import android.stats.connectivity.ProbeResult; import android.stats.connectivity.ProbeType; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants; import android.telephony.CellIdentityNr; import android.telephony.CellIdentityNr; import android.telephony.CellInfo; import android.telephony.CellInfo; Loading @@ -155,6 +157,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.StateMachine; Loading @@ -168,6 +171,7 @@ import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.metrics.NetworkValidationMetrics; import com.android.networkstack.netlink.TcpSocketTracker; import com.android.networkstack.netlink.TcpSocketTracker; import com.android.networkstack.util.DnsUtils; import com.android.networkstack.util.DnsUtils; import com.android.server.NetworkStackService.NetworkStackServiceManager; import com.android.server.NetworkStackService.NetworkStackServiceManager; Loading Loading @@ -502,6 +506,10 @@ public class NetworkMonitor extends StateMachine { private final boolean mPrivateIpNoInternetEnabled; private final boolean mPrivateIpNoInternetEnabled; @GuardedBy("mNetworkValidationMetrics") private final NetworkValidationMetrics mNetworkValidationMetrics = new NetworkValidationMetrics(); private int getCallbackVersion(INetworkMonitorCallbacks cb) { private int getCallbackVersion(INetworkMonitorCallbacks cb) { int version; int version; try { try { Loading Loading @@ -778,6 +786,31 @@ public class NetworkMonitor extends StateMachine { } } } } private void recordMetricsReset(@Nullable NetworkCapabilities nc) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.reset(nc); } } private void recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result, CaptivePortalDataShim capportData) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, capportData); } } private void recordMetricsValidationResult(int result, String redirectUrl) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setValidationResult(result, redirectUrl); } } private void recordMetricsValidationStats() { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.sendValidationStats(); } } // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // does not entail any real state (hence no enter() or exit() routines). // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { private class DefaultState extends State { Loading @@ -790,6 +823,7 @@ public class NetworkMonitor extends StateMachine { transitionTo(mEvaluatingState); transitionTo(mEvaluatingState); return HANDLED; return HANDLED; case CMD_NETWORK_DISCONNECTED: case CMD_NETWORK_DISCONNECTED: recordMetricsValidationStats(); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); quit(); quit(); return HANDLED; return HANDLED; Loading Loading @@ -941,6 +975,7 @@ public class NetworkMonitor extends StateMachine { initSocketTrackingIfRequired(); initSocketTrackingIfRequired(); // start periodical polling. // start periodical polling. sendTcpPollingEvent(); sendTcpPollingEvent(); recordMetricsValidationStats(); } } private void initSocketTrackingIfRequired() { private void initSocketTrackingIfRequired() { Loading @@ -960,6 +995,9 @@ public class NetworkMonitor extends StateMachine { transitionTo(mValidatedState); transitionTo(mValidatedState); break; break; case CMD_EVALUATE_PRIVATE_DNS: case CMD_EVALUATE_PRIVATE_DNS: // TODO: this causes reevaluation of a single probe that is not counted in // metrics. Add support for such reevaluation probes in metrics, and log them // separately. transitionTo(mEvaluatingPrivateDnsState); transitionTo(mEvaluatingPrivateDnsState); break; break; case EVENT_DNS_NOTIFICATION: case EVENT_DNS_NOTIFICATION: Loading Loading @@ -1288,6 +1326,7 @@ public class NetworkMonitor extends StateMachine { sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); mValidations++; mValidations++; recordMetricsValidationStats(); } } @Override @Override Loading Loading @@ -1319,6 +1358,10 @@ public class NetworkMonitor extends StateMachine { notifyPrivateDnsConfigResolved(); notifyPrivateDnsConfigResolved(); } else { } else { handlePrivateDnsEvaluationFailure(); handlePrivateDnsEvaluationFailure(); // The private DNS probe fails-fast if the server hostname cannot // be resolved. Record it as a failure with zero latency. recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, 0 /* latency */, ProbeResult.PR_FAILURE, null /* capportData */); break; break; } } } } Loading Loading @@ -1428,6 +1471,8 @@ public class NetworkMonitor extends StateMachine { validationLog(PROBE_PRIVDNS, host, validationLog(PROBE_PRIVDNS, host, String.format("%dus - Error: %s", time, uhe.getMessage())); String.format("%dus - Error: %s", time, uhe.getMessage())); } } recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); return success; return success; } } Loading @@ -1438,6 +1483,8 @@ public class NetworkMonitor extends StateMachine { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); recordMetricsReset(mNetworkCapabilities); if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { //Don't continue to blame UID forever. //Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); TrafficStats.clearThreadStatsUid(); Loading Loading @@ -1519,6 +1566,7 @@ public class NetworkMonitor extends StateMachine { private class WaitingForNextProbeState extends State { private class WaitingForNextProbeState extends State { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); scheduleNextProbe(); scheduleNextProbe(); } } Loading Loading @@ -2269,6 +2317,8 @@ public class NetworkMonitor extends StateMachine { // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); return CaptivePortalProbeResult.PRIVATE_IP; return CaptivePortalProbeResult.PRIVATE_IP; } } return sendHttpProbe(url, probeType, null); return sendHttpProbe(url, probeType, null); Loading Loading @@ -2300,6 +2350,9 @@ public class NetworkMonitor extends StateMachine { result = ValidationProbeEvent.DNS_FAILURE; result = ValidationProbeEvent.DNS_FAILURE; } } final long latency = watch.stop(); final long latency = watch.stop(); recordMetricsProbeEvent(ProbeType.PT_DNS, latency, (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); return addresses; return addresses; } } Loading Loading @@ -2417,12 +2470,17 @@ public class NetworkMonitor extends StateMachine { } } logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); final CaptivePortalProbeResult probeResult; if (probeSpec == null) { if (probeSpec == null) { return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(), probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl, 1 << probeType); url.toString(), 1 << probeType); } else { } else { return probeSpec.getResult(httpResponseCode, redirectUrl); probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); } } recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), null /* capportData */); return probeResult; } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -2576,8 +2634,7 @@ public class NetworkMonitor extends StateMachine { super(properties, proxy, url, captivePortalApiUrl); super(properties, proxy, url, captivePortalApiUrl); } } private CaptivePortalDataShim tryCapportApiProbe() { private CaptivePortalDataShim sendCapportApiProbe() { if (mCaptivePortalApiUrl == null) return null; validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); final String apiContent; final String apiContent; Loading Loading @@ -2616,26 +2673,38 @@ public class NetworkMonitor extends StateMachine { try { try { final JSONObject info = new JSONObject(apiContent); final JSONObject info = new JSONObject(apiContent); return CaptivePortalDataShimImpl.fromJson(info); final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info); if (capportData != null && capportData.isCaptive() && capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return null; } return capportData; } catch (JSONException e) { } catch (JSONException e) { validationLog("Could not parse capport API JSON: " + e.getMessage()); validationLog("Could not parse capport API JSON: " + e.getMessage()); return null; return null; } catch (UnsupportedApiLevelException e) { } catch (UnsupportedApiLevelException e) { // This should never happen because LinkProperties would not have a capport URL // before R. validationLog("Platform API too low to support capport API"); validationLog("Platform API too low to support capport API"); return null; return null; } } } } private CaptivePortalDataShim tryCapportApiProbe() { if (mCaptivePortalApiUrl == null) return null; final Stopwatch capportApiWatch = new Stopwatch().start(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData); return capportData; } @Override @Override protected CaptivePortalProbeResult sendProbe() { protected CaptivePortalProbeResult sendProbe() { final CaptivePortalDataShim capportData = tryCapportApiProbe(); final CaptivePortalDataShim capportData = tryCapportApiProbe(); if (capportData != null && capportData.isCaptive()) { if (capportData != null && capportData.isCaptive()) { if (capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), null /* capportData */); } final String loginUrlString = capportData.getUserPortalUrl().toString(); final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app // Starting from R (where CaptivePortalData was introduced), the captive portal app // delegates to NetworkMonitor for verifying when the network validates instead of // delegates to NetworkMonitor for verifying when the network validates instead of Loading Loading @@ -3357,6 +3426,7 @@ public class NetworkMonitor extends StateMachine { p.redirectUrl = redirectUrl; p.redirectUrl = redirectUrl; p.timestampMillis = SystemClock.elapsedRealtime(); p.timestampMillis = SystemClock.elapsedRealtime(); notifyNetworkTested(p); notifyNetworkTested(p); recordMetricsValidationResult(result, redirectUrl); } } @VisibleForTesting @VisibleForTesting Loading Loading
apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java +10 −0 Original line number Original line Diff line number Diff line Loading @@ -92,6 +92,16 @@ public class CaptivePortalDataShimImpl return mData.isCaptive(); return mData.isCaptive(); } } @Override public long getByteLimit() { return mData.getByteLimit(); } @Override public long getExpiryTimeMillis() { return mData.getExpiryTimeMillis(); } @Override @Override public Uri getUserPortalUrl() { public Uri getUserPortalUrl() { return mData.getUserPortalUrl(); return mData.getUserPortalUrl(); Loading
apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java +10 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,16 @@ public interface CaptivePortalDataShim { */ */ boolean isCaptive(); boolean isCaptive(); /** * @see android.net.CaptivePortalData#getByteLimit() */ long getByteLimit(); /** * @see android.net.CaptivePortalData#getExpiryTimeMillis() */ long getExpiryTimeMillis(); /** /** * @see android.net.CaptivePortalData#getUserPortalUrl() * @see android.net.CaptivePortalData#getUserPortalUrl() */ */ Loading
common/networkstackclient/Android.bp +4 −2 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ aidl_interface { apex_available: [ apex_available: [ "//apex_available:platform", "//apex_available:platform", "com.android.wifi", "com.android.wifi", "com.android.bluetooth.updatable", ], ], // this is part of updatable modules(NetworkStack) which targets 29(Q) // this is part of updatable modules(NetworkStack) which targets 29(Q) min_sdk_version: "29", min_sdk_version: "29", Loading Loading @@ -92,6 +93,7 @@ aidl_interface { "//apex_available:platform", "//apex_available:platform", "com.android.bluetooth.updatable", "com.android.bluetooth.updatable", "com.android.wifi", "com.android.wifi", "com.android.tethering", ], ], // this is part of updatable modules(NetworkStack) which targets 29(Q) // this is part of updatable modules(NetworkStack) which targets 29(Q) min_sdk_version: "29", min_sdk_version: "29", Loading Loading @@ -133,8 +135,8 @@ java_library { "src/android/net/shared/**/*.java", "src/android/net/shared/**/*.java", ], ], static_libs: [ static_libs: [ "ipmemorystore-aidl-interfaces-java", "ipmemorystore-aidl-interfaces-unstable-java", "networkstack-aidl-interfaces-java", "networkstack-aidl-interfaces-unstable-java", ], ], visibility: [ visibility: [ "//frameworks/base/packages/Tethering", "//frameworks/base/packages/Tethering", Loading
src/com/android/networkstack/metrics/NetworkValidationMetrics.java 0 → 100644 +220 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.networkstack.metrics; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static java.lang.System.currentTimeMillis; import android.net.INetworkMonitor; import android.net.NetworkCapabilities; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.ValidationProbeEvent; import android.net.util.NetworkStackUtils; import android.net.util.Stopwatch; import android.stats.connectivity.ProbeResult; import android.stats.connectivity.ProbeType; import android.stats.connectivity.TransportType; import android.stats.connectivity.ValidationResult; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.networkstack.apishim.common.CaptivePortalDataShim; /** * Class to record the network validation into statsd. * 1. Fill in NetworkValidationReported proto. * 2. Write the NetworkValidationReported proto into statsd. * @hide */ public class NetworkValidationMetrics { private final NetworkValidationReported.Builder mStatsBuilder = NetworkValidationReported.newBuilder(); private final ProbeEvents.Builder mProbeEventsBuilder = ProbeEvents.newBuilder(); private final CapportApiData.Builder mCapportApiDataBuilder = CapportApiData.newBuilder(); private final Stopwatch mWatch = new Stopwatch(); private int mValidationIndex = 0; // Define a maximum size that can store events. public static final int MAX_PROBE_EVENTS_COUNT = 20; /** * Reset this NetworkValidationMetrics. */ public void reset(@Nullable NetworkCapabilities nc) { mStatsBuilder.clear(); mProbeEventsBuilder.clear(); mCapportApiDataBuilder.clear(); mWatch.restart(); mStatsBuilder.setTransportType(getTransportTypeFromNC(nc)); mValidationIndex++; } /** * Returns the enum TransportType * * @param NetworkCapabilities * @return the TransportType which is defined in * core/proto/android/stats/connectivity/network_stack.proto */ @VisibleForTesting public static TransportType getTransportTypeFromNC( @Nullable NetworkCapabilities nc) { if (nc == null) return TransportType.TT_UNKNOWN; boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR); boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI); boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH); boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET); boolean hasVpn = nc.hasTransport(TRANSPORT_VPN); boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE); boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN); if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN; if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI; if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR; if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH; if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET; if (hasWifiAware) return TransportType.TT_WIFI_AWARE; if (hasLopan) return TransportType.TT_LOWPAN; return TransportType.TT_UNKNOWN; } /** * Map {@link ValidationProbeEvent} to {@link ProbeType}. */ public static ProbeType probeTypeToEnum(final int probeType) { switch(probeType) { case ValidationProbeEvent.PROBE_DNS: return ProbeType.PT_DNS; case ValidationProbeEvent.PROBE_HTTP: return ProbeType.PT_HTTP; case ValidationProbeEvent.PROBE_HTTPS: return ProbeType.PT_HTTPS; case ValidationProbeEvent.PROBE_PAC: return ProbeType.PT_PAC; case ValidationProbeEvent.PROBE_FALLBACK: return ProbeType.PT_FALLBACK; case ValidationProbeEvent.PROBE_PRIVDNS: return ProbeType.PT_PRIVDNS; default: return ProbeType.PT_UNKNOWN; } } /** * Map {@link CaptivePortalProbeResult} to {@link ProbeResult}. */ public static ProbeResult httpProbeResultToEnum(final CaptivePortalProbeResult result) { if (result == null) return ProbeResult.PR_UNKNOWN; if (result.isSuccessful()) { return ProbeResult.PR_SUCCESS; } else if (result.isDnsPrivateIpResponse()) { return ProbeResult.PR_PRIVATE_IP_DNS; } else if (result.isFailed()) { return ProbeResult.PR_FAILURE; } else if (result.isPortal()) { return ProbeResult.PR_PORTAL; } else { return ProbeResult.PR_UNKNOWN; } } /** * Map validation result (as per INetworkMonitor) to {@link ValidationResult}. */ @VisibleForTesting public static ValidationResult validationResultToEnum(int result, String redirectUrl) { if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) { return ValidationResult.VR_SUCCESS; } else if (redirectUrl != null) { return ValidationResult.VR_PORTAL; } else if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL) != 0) { return ValidationResult.VR_PARTIAL; } else { return ValidationResult.VR_FAILURE; } } /** * Write each network probe event to mProbeEventsBuilder. */ public void setProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result, @Nullable final CaptivePortalDataShim capportData) { // When the number of ProbeEvents of mProbeEventsBuilder exceeds // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent. if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return; int latencyUs = NetworkStackUtils.saturatedCast(durationUs); final ProbeEvent.Builder probeEventBuilder = ProbeEvent.newBuilder() .setLatencyMicros(latencyUs) .setProbeType(type) .setProbeResult(result); if (capportData != null) { final long secondsRemaining = (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000; mCapportApiDataBuilder .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining)) .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit())) .setHasPortalUrl((capportData.getUserPortalUrl() != null)) .setHasVenueInfo((capportData.getVenueInfoUrl() != null)); probeEventBuilder.setCapportApiData(mCapportApiDataBuilder); } mProbeEventsBuilder.addProbeEvent(probeEventBuilder); } /** * Write the network validation info to mStatsBuilder. */ public void setValidationResult(int result, String redirectUrl) { mStatsBuilder.setValidationResult(validationResultToEnum(result, redirectUrl)); } /** * Write the NetworkValidationReported proto to statsd. */ public NetworkValidationReported sendValidationStats() { if (!mWatch.isStarted()) return null; mStatsBuilder.setProbeEvents(mProbeEventsBuilder); mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop())); mStatsBuilder.setValidationIndex(mValidationIndex); // write a random value(0 ~ 999) for sampling. mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); final NetworkValidationReported mStats = mStatsBuilder.build(); final byte[] probeEvents = mStats.getProbeEvents().toByteArray(); NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED, mStats.getTransportType().getNumber(), probeEvents, mStats.getValidationResult().getNumber(), mStats.getLatencyMicros(), mStats.getValidationIndex(), mStats.getRandomNumber()); mWatch.reset(); return mStats; } }
src/com/android/server/connectivity/NetworkMonitor.java +82 −12 Original line number Original line Diff line number Diff line Loading @@ -129,6 +129,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings; import android.stats.connectivity.ProbeResult; import android.stats.connectivity.ProbeType; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants; import android.telephony.CellIdentityNr; import android.telephony.CellIdentityNr; import android.telephony.CellInfo; import android.telephony.CellInfo; Loading @@ -155,6 +157,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.StateMachine; Loading @@ -168,6 +171,7 @@ import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.metrics.NetworkValidationMetrics; import com.android.networkstack.netlink.TcpSocketTracker; import com.android.networkstack.netlink.TcpSocketTracker; import com.android.networkstack.util.DnsUtils; import com.android.networkstack.util.DnsUtils; import com.android.server.NetworkStackService.NetworkStackServiceManager; import com.android.server.NetworkStackService.NetworkStackServiceManager; Loading Loading @@ -502,6 +506,10 @@ public class NetworkMonitor extends StateMachine { private final boolean mPrivateIpNoInternetEnabled; private final boolean mPrivateIpNoInternetEnabled; @GuardedBy("mNetworkValidationMetrics") private final NetworkValidationMetrics mNetworkValidationMetrics = new NetworkValidationMetrics(); private int getCallbackVersion(INetworkMonitorCallbacks cb) { private int getCallbackVersion(INetworkMonitorCallbacks cb) { int version; int version; try { try { Loading Loading @@ -778,6 +786,31 @@ public class NetworkMonitor extends StateMachine { } } } } private void recordMetricsReset(@Nullable NetworkCapabilities nc) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.reset(nc); } } private void recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result, CaptivePortalDataShim capportData) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, capportData); } } private void recordMetricsValidationResult(int result, String redirectUrl) { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.setValidationResult(result, redirectUrl); } } private void recordMetricsValidationStats() { synchronized (mNetworkValidationMetrics) { mNetworkValidationMetrics.sendValidationStats(); } } // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // does not entail any real state (hence no enter() or exit() routines). // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { private class DefaultState extends State { Loading @@ -790,6 +823,7 @@ public class NetworkMonitor extends StateMachine { transitionTo(mEvaluatingState); transitionTo(mEvaluatingState); return HANDLED; return HANDLED; case CMD_NETWORK_DISCONNECTED: case CMD_NETWORK_DISCONNECTED: recordMetricsValidationStats(); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); quit(); quit(); return HANDLED; return HANDLED; Loading Loading @@ -941,6 +975,7 @@ public class NetworkMonitor extends StateMachine { initSocketTrackingIfRequired(); initSocketTrackingIfRequired(); // start periodical polling. // start periodical polling. sendTcpPollingEvent(); sendTcpPollingEvent(); recordMetricsValidationStats(); } } private void initSocketTrackingIfRequired() { private void initSocketTrackingIfRequired() { Loading @@ -960,6 +995,9 @@ public class NetworkMonitor extends StateMachine { transitionTo(mValidatedState); transitionTo(mValidatedState); break; break; case CMD_EVALUATE_PRIVATE_DNS: case CMD_EVALUATE_PRIVATE_DNS: // TODO: this causes reevaluation of a single probe that is not counted in // metrics. Add support for such reevaluation probes in metrics, and log them // separately. transitionTo(mEvaluatingPrivateDnsState); transitionTo(mEvaluatingPrivateDnsState); break; break; case EVENT_DNS_NOTIFICATION: case EVENT_DNS_NOTIFICATION: Loading Loading @@ -1288,6 +1326,7 @@ public class NetworkMonitor extends StateMachine { sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); mValidations++; mValidations++; recordMetricsValidationStats(); } } @Override @Override Loading Loading @@ -1319,6 +1358,10 @@ public class NetworkMonitor extends StateMachine { notifyPrivateDnsConfigResolved(); notifyPrivateDnsConfigResolved(); } else { } else { handlePrivateDnsEvaluationFailure(); handlePrivateDnsEvaluationFailure(); // The private DNS probe fails-fast if the server hostname cannot // be resolved. Record it as a failure with zero latency. recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, 0 /* latency */, ProbeResult.PR_FAILURE, null /* capportData */); break; break; } } } } Loading Loading @@ -1428,6 +1471,8 @@ public class NetworkMonitor extends StateMachine { validationLog(PROBE_PRIVDNS, host, validationLog(PROBE_PRIVDNS, host, String.format("%dus - Error: %s", time, uhe.getMessage())); String.format("%dus - Error: %s", time, uhe.getMessage())); } } recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); return success; return success; } } Loading @@ -1438,6 +1483,8 @@ public class NetworkMonitor extends StateMachine { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); recordMetricsReset(mNetworkCapabilities); if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { //Don't continue to blame UID forever. //Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); TrafficStats.clearThreadStatsUid(); Loading Loading @@ -1519,6 +1566,7 @@ public class NetworkMonitor extends StateMachine { private class WaitingForNextProbeState extends State { private class WaitingForNextProbeState extends State { @Override @Override public void enter() { public void enter() { recordMetricsValidationStats(); scheduleNextProbe(); scheduleNextProbe(); } } Loading Loading @@ -2269,6 +2317,8 @@ public class NetworkMonitor extends StateMachine { // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); return CaptivePortalProbeResult.PRIVATE_IP; return CaptivePortalProbeResult.PRIVATE_IP; } } return sendHttpProbe(url, probeType, null); return sendHttpProbe(url, probeType, null); Loading Loading @@ -2300,6 +2350,9 @@ public class NetworkMonitor extends StateMachine { result = ValidationProbeEvent.DNS_FAILURE; result = ValidationProbeEvent.DNS_FAILURE; } } final long latency = watch.stop(); final long latency = watch.stop(); recordMetricsProbeEvent(ProbeType.PT_DNS, latency, (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : ProbeResult.PR_FAILURE, null /* capportData */); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); return addresses; return addresses; } } Loading Loading @@ -2417,12 +2470,17 @@ public class NetworkMonitor extends StateMachine { } } logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); final CaptivePortalProbeResult probeResult; if (probeSpec == null) { if (probeSpec == null) { return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(), probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl, 1 << probeType); url.toString(), 1 << probeType); } else { } else { return probeSpec.getResult(httpResponseCode, redirectUrl); probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); } } recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType), probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), null /* capportData */); return probeResult; } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -2576,8 +2634,7 @@ public class NetworkMonitor extends StateMachine { super(properties, proxy, url, captivePortalApiUrl); super(properties, proxy, url, captivePortalApiUrl); } } private CaptivePortalDataShim tryCapportApiProbe() { private CaptivePortalDataShim sendCapportApiProbe() { if (mCaptivePortalApiUrl == null) return null; validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); final String apiContent; final String apiContent; Loading Loading @@ -2616,26 +2673,38 @@ public class NetworkMonitor extends StateMachine { try { try { final JSONObject info = new JSONObject(apiContent); final JSONObject info = new JSONObject(apiContent); return CaptivePortalDataShimImpl.fromJson(info); final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info); if (capportData != null && capportData.isCaptive() && capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return null; } return capportData; } catch (JSONException e) { } catch (JSONException e) { validationLog("Could not parse capport API JSON: " + e.getMessage()); validationLog("Could not parse capport API JSON: " + e.getMessage()); return null; return null; } catch (UnsupportedApiLevelException e) { } catch (UnsupportedApiLevelException e) { // This should never happen because LinkProperties would not have a capport URL // before R. validationLog("Platform API too low to support capport API"); validationLog("Platform API too low to support capport API"); return null; return null; } } } } private CaptivePortalDataShim tryCapportApiProbe() { if (mCaptivePortalApiUrl == null) return null; final Stopwatch capportApiWatch = new Stopwatch().start(); final CaptivePortalDataShim capportData = sendCapportApiProbe(); recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, capportData); return capportData; } @Override @Override protected CaptivePortalProbeResult sendProbe() { protected CaptivePortalProbeResult sendProbe() { final CaptivePortalDataShim capportData = tryCapportApiProbe(); final CaptivePortalDataShim capportData = tryCapportApiProbe(); if (capportData != null && capportData.isCaptive()) { if (capportData != null && capportData.isCaptive()) { if (capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), null /* capportData */); } final String loginUrlString = capportData.getUserPortalUrl().toString(); final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app // Starting from R (where CaptivePortalData was introduced), the captive portal app // delegates to NetworkMonitor for verifying when the network validates instead of // delegates to NetworkMonitor for verifying when the network validates instead of Loading Loading @@ -3357,6 +3426,7 @@ public class NetworkMonitor extends StateMachine { p.redirectUrl = redirectUrl; p.redirectUrl = redirectUrl; p.timestampMillis = SystemClock.elapsedRealtime(); p.timestampMillis = SystemClock.elapsedRealtime(); notifyNetworkTested(p); notifyNetworkTested(p); recordMetricsValidationResult(result, redirectUrl); } } @VisibleForTesting @VisibleForTesting Loading