diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b334b26fa8..60a30d3317 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -64,6 +64,7 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.database.ContentObserver; import android.net.CaptivePortal; +import android.net.CaptivePortalData; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityDiagnosticsManager.DataStallReport; @@ -545,6 +546,14 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public static final int EVENT_PROBE_STATUS_CHANGED = 46; + /** + * Event for NetworkMonitor to inform ConnectivityService that captive portal data has changed. + * arg1 = unused + * arg2 = netId + * obj = captive portal data + */ + private static final int EVENT_CAPPORT_DATA_CHANGED = 47; + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. @@ -2824,6 +2833,12 @@ public class ConnectivityService extends IConnectivityManager.Stub updatePrivateDns(nai, (PrivateDnsConfig) msg.obj); break; } + case EVENT_CAPPORT_DATA_CHANGED: { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); + if (nai == null) break; + handleCaptivePortalDataUpdate(nai, (CaptivePortalData) msg.obj); + break; + } } return true; } @@ -2990,6 +3005,13 @@ public class ConnectivityService extends IConnectivityManager.Stub probesCompleted, probesSucceeded, new Integer(mNetId))); } + @Override + public void notifyCaptivePortalDataChanged(CaptivePortalData data) { + mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( + EVENT_CAPPORT_DATA_CHANGED, + 0, mNetId, data)); + } + @Override public void showProvisioningNotification(String action, String packageName) { final Intent intent = new Intent(action); @@ -3118,6 +3140,13 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } + private void handleCaptivePortalDataUpdate(@NonNull final NetworkAgentInfo nai, + @Nullable final CaptivePortalData data) { + nai.captivePortalData = data; + // CaptivePortalData will be merged into LinkProperties from NetworkAgentInfo + handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); + } + /** * Updates the linger state from the network requests inside the NAI. * @param nai the agent info to update @@ -5838,6 +5867,10 @@ public class ConnectivityService extends IConnectivityManager.Stub updateWakeOnLan(newLp); + // Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo, + // it is not contained in LinkProperties sent from NetworkAgents so needs to be merged here. + newLp.setCaptivePortalData(networkAgent.captivePortalData); + // TODO - move this check to cover the whole function if (!Objects.equals(newLp, oldLp)) { synchronized (networkAgent) { @@ -6898,6 +6931,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // worry about multiple different substates of CONNECTED. newInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, info.getReason(), info.getExtraInfo()); + } else if (!suspended && info.getDetailedState() == NetworkInfo.DetailedState.SUSPENDED) { + // SUSPENDED state is currently only overridden from CONNECTED state. In the case the + // network agent is created, then goes to suspended, then goes out of suspended without + // ever setting connected. Check if network agent is ever connected to update the state. + newInfo.setDetailedState(nai.everConnected + ? NetworkInfo.DetailedState.CONNECTED + : NetworkInfo.DetailedState.CONNECTING, + info.getReason(), + info.getExtraInfo()); } newInfo.setRoaming(!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)); return newInfo; diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 592be2f91b..4612cfd0f7 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -21,6 +21,7 @@ import static android.net.NetworkCapabilities.transportNamesOf; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.net.CaptivePortalData; import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; @@ -167,6 +168,10 @@ public class NetworkAgentInfo implements Comparable { // Set to true when partial connectivity was detected. public boolean partialConnectivity; + // Captive portal info of the network, if any. + // Obtained by ConnectivityService and merged into NetworkAgent-provided information. + public CaptivePortalData captivePortalData; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being // satisfied by a higher-scoring network. so as to allow communication to wrap up before the // network is taken down. This usually only happens to the default network. Lingering ends with diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java index d0aabf95d5..1ae7dc5c36 100644 --- a/services/core/java/com/android/server/connectivity/NetworkRanker.java +++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.NetworkRequest; +import java.util.ArrayList; import java.util.Collection; /** @@ -31,15 +32,15 @@ public class NetworkRanker { /** * Find the best network satisfying this request among the list of passed networks. */ - // Almost equivalent to Collections.max(nais), but allows returning null if no network - // satisfies the request. @Nullable public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request, @NonNull final Collection nais) { + final ArrayList candidates = new ArrayList<>(nais); + candidates.removeIf(nai -> !nai.satisfies(request)); + NetworkAgentInfo bestNetwork = null; int bestScore = Integer.MIN_VALUE; - for (final NetworkAgentInfo nai : nais) { - if (!nai.satisfies(request)) continue; + for (final NetworkAgentInfo nai : candidates) { if (nai.getCurrentScore() > bestScore) { bestNetwork = nai; bestScore = nai.getCurrentScore(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 968f5523d8..2e59966698 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -799,6 +799,14 @@ public class ConnectivityServiceTest { mProbesSucceeded = probesSucceeded; } + void notifyCaptivePortalDataChanged(CaptivePortalData data) { + try { + mNmCallbacks.notifyCaptivePortalDataChanged(data); + } catch (RemoteException e) { + throw new AssertionError("This cannot happen", e); + } + } + public String waitForRedirectUrl() { assertTrue(mNetworkStatusReceived.block(TIMEOUT_MS)); return mRedirectUrl; @@ -1845,18 +1853,21 @@ public class ConnectivityServiceTest { final Uri capportUrl = Uri.parse("https://capport.example.com/api"); final CaptivePortalData capportData = new CaptivePortalData.Builder() .setCaptive(true).build(); - newLp.setCaptivePortalApiUrl(capportUrl); - newLp.setCaptivePortalData(capportData); - mWiFiNetworkAgent.sendLinkProperties(newLp); final Uri expectedCapportUrl = sanitized ? null : capportUrl; - final CaptivePortalData expectedCapportData = sanitized ? null : capportData; + newLp.setCaptivePortalApiUrl(capportUrl); + mWiFiNetworkAgent.sendLinkProperties(newLp); callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> - Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()) - && Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> - Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()) - && Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); + + final CaptivePortalData expectedCapportData = sanitized ? null : capportData; + mWiFiNetworkAgent.notifyCaptivePortalDataChanged(capportData); + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl()); @@ -2810,6 +2821,40 @@ public class ConnectivityServiceTest { assertNoCallbacks(captivePortalCallback, validatedCallback); } + @Test + public void testCaptivePortalApi() throws Exception { + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final String redirectUrl = "http://example.com/firstPath"; + + mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final CaptivePortalData testData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(redirectUrl)) + .setBytesRemaining(12345L) + .build(); + + mWiFiNetworkAgent.notifyCaptivePortalDataChanged(testData); + + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData())); + + final LinkProperties newLps = new LinkProperties(); + newLps.setMtu(1234); + mWiFiNetworkAgent.sendLinkProperties(newLps); + // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234); + } + private NetworkRequest.Builder newWifiRequestBuilder() { return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); } @@ -3154,6 +3199,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState()); // Register a garden variety default network request. TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback(); @@ -3169,6 +3215,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState()); dfltNetworkCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(dfltNetworkCallback);