Loading services/core/java/com/android/server/ConnectivityService.java +40 −49 Original line number Diff line number Diff line Loading @@ -2064,12 +2064,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // if it's awaiting captive portal login, or if validation failed), this // may trigger a re-evaluation of the network. private void unlinger(NetworkAgentInfo nai) { nai.networkLingered.clear(); if (!nai.lingering) return; nai.lingering = false; if (VDBG) log("Canceling linger of " + nai.name()); // If network has never been validated, it cannot have been lingered, so don't bother // needlessly triggering a re-evaluation. if (!nai.everValidated) return; nai.networkLingered.clear(); nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); } Loading Loading @@ -2224,23 +2222,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Is nai unneeded by all NetworkRequests (and should be disconnected)? // For validated Networks this is simply whether it is satsifying any NetworkRequests. // For unvalidated Networks this is whether it is satsifying any NetworkRequests or // were it to become validated, would it have a chance of satisfying any NetworkRequests. // This is whether it is satisfying any NetworkRequests or were it to become validated, // would it have a chance of satisfying any NetworkRequests. private boolean unneeded(NetworkAgentInfo nai) { if (!nai.created || nai.isVPN() || nai.lingering) return false; boolean unneeded = true; if (nai.everValidated) { for (int i = 0; i < nai.networkRequests.size() && unneeded; i++) { final NetworkRequest nr = nai.networkRequests.valueAt(i); try { if (isRequest(nr)) unneeded = false; } catch (Exception e) { loge("Request " + nr + " not found in mNetworkRequests."); loge(" it came from request list of " + nai.name()); } } } else { for (NetworkRequestInfo nri : mNetworkRequests.values()) { // If this Network is already the highest scoring Network for a request, or if // there is hope for it to become one if it validated, then it is needed. Loading @@ -2255,12 +2240,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // WiFi ends up validating and out scoring cellular. mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() < nai.getCurrentScoreAsValidated())) { unneeded = false; break; } return false; } } return unneeded; return true; } private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { Loading Loading @@ -4004,29 +3987,29 @@ public class ConnectivityService extends IConnectivityManager.Stub * augmented with any stateful capabilities implied from {@code networkAgent} * (e.g., validated status and captive portal status). * * @param networkAgent the network having its capabilities updated. * @param nai the network having its capabilities updated. * @param networkCapabilities the new network capabilities. */ private void updateCapabilities(NetworkAgentInfo networkAgent, NetworkCapabilities networkCapabilities) { private void updateCapabilities(NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) { // Don't modify caller's NetworkCapabilities. networkCapabilities = new NetworkCapabilities(networkCapabilities); if (networkAgent.lastValidated) { if (nai.lastValidated) { networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); } else { networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED); } if (networkAgent.lastCaptivePortalDetected) { if (nai.lastCaptivePortalDetected) { networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) { synchronized (networkAgent) { networkAgent.networkCapabilities = networkCapabilities; if (!Objects.equals(nai.networkCapabilities, networkCapabilities)) { final int oldScore = nai.getCurrentScore(); synchronized (nai) { nai.networkCapabilities = networkCapabilities; } rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore()); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED); rematchAllNetworksAndRequests(nai, oldScore); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } } Loading Loading @@ -4256,9 +4239,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Linger any networks that are no longer needed. for (NetworkAgentInfo nai : affectedNetworks) { if (nai.everValidated && unneeded(nai)) { if (nai.lingering) { // Already lingered. Nothing to do. This can only happen if "nai" is in // "affectedNetworks" twice. The reasoning being that to get added to // "affectedNetworks", "nai" must have been satisfying a NetworkRequest // (i.e. not lingered) so it could have only been lingered by this loop. // unneeded(nai) will be false and we'll call unlinger() below which would // be bad, so handle it here. } else if (unneeded(nai)) { linger(nai); } else { // Clear nai.networkLingered we might have added above. unlinger(nai); } } Loading Loading @@ -4292,7 +4283,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(), oldDefaultNetwork, true); } mDefaultInetConditionPublished = newNetwork.everValidated ? 100 : 0; mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork); notifyLockdownVpn(newNetwork); } Loading services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +64 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import android.content.Context; import android.net.LinkProperties; import android.net.Network; Loading @@ -39,6 +41,62 @@ import java.util.Comparator; * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests * interested in using it. Default sort order is descending by score. */ // States of a network: // -------------------- // 1. registered, uncreated, disconnected, unvalidated // This state is entered when a NetworkFactory registers a NetworkAgent in any state except // the CONNECTED state. // 2. registered, uncreated, connected, unvalidated // This state is entered when a registered NetworkAgent transitions to the CONNECTED state // ConnectivityService will tell netd to create the network and immediately transition to // state #3. // 3. registered, created, connected, unvalidated // If this network can satsify the default NetworkRequest, then NetworkMonitor will // probe for Internet connectivity. // If this network cannot satisfy the default NetworkRequest, it will immediately be // transitioned to state #4. // A network may remain in this state if NetworkMonitor fails to find Internet connectivity, // for example: // a. a captive portal is present, or // b. a WiFi router whose Internet backhaul is down, or // c. a wireless connection stops transfering packets temporarily (e.g. device is in elevator // or tunnel) but does not disconnect from the AP/cell tower, or // d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes. // 4. registered, created, connected, validated // // The device's default network connection: // ---------------------------------------- // Networks in states #3 and #4 may be used as a device's default network connection if they // satisfy the default NetworkRequest. // A network, that satisfies the default NetworkRequest, in state #4 should always be chosen // in favor of a network, that satisfies the default NetworkRequest, in state #3. // When deciding between two networks, that both satisfy the default NetworkRequest, to select // for the default network connection, the one with the higher score should be chosen. // // When a network disconnects: // --------------------------- // If a network's transport disappears, for example: // a. WiFi turned off, or // b. cellular data turned off, or // c. airplane mode is turned on, or // d. a wireless connection disconnects from AP/cell tower entirely (e.g. device is out of range // of AP for an extended period of time, or switches to another AP without roaming) // then that network can transition from any state (#1-#4) to unregistered. This happens by // the transport disconnecting their NetworkAgent's AsyncChannel with ConnectivityManager. // ConnectivityService also tells netd to destroy the network. // // When ConnectivityService disconnects a network: // ----------------------------------------------- // If a network has no chance of satisfying any requests (even if it were to become validated // and enter state #4), ConnectivityService will disconnect the NetworkAgent's AsyncChannel. // If the network ever for any period of time had satisfied a NetworkRequest (i.e. had been // the highest scoring that satisfied the NetworkRequest's constraints), but is no longer the // highest scoring network for any NetworkRequest, then there will be a 30s pause before // ConnectivityService disconnects the NetworkAgent's AsyncChannel. During this pause the // network is considered "lingering". This pause exists to allow network communication to be // wrapped up rather than abruptly terminated. During this pause if the network begins satisfying // a NetworkRequest, ConnectivityService will cancel the future disconnection of the NetworkAgent's // AsyncChannel, and the network is no longer considered "lingering". public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkInfo networkInfo; // This Network object should always be used if possible, so as to encourage reuse of the Loading Loading @@ -156,7 +214,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } int score = currentScore; if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY; // Use NET_CAPABILITY_VALIDATED here instead of lastValidated, this allows // ConnectivityService.updateCapabilities() to compute the old score prior to updating // networkCapabilities (with a potentially different validated state). if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) { score -= UNVALIDATED_SCORE_PENALTY; } if (score < 0) score = 0; return score; } Loading services/core/java/com/android/server/connectivity/NetworkMonitor.java +6 −3 Original line number Diff line number Diff line Loading @@ -585,9 +585,12 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_NETWORK_CONNECTED: log("Unlingered"); // Go straight to active as we've already evaluated. // If already validated, go straight to validated state. if (mNetworkAgentInfo.lastValidated) { transitionTo(mValidatedState); return HANDLED; } return NOT_HANDLED; case CMD_LINGER_EXPIRED: if (message.arg1 != mLingerToken) return HANDLED; Loading services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +207 −4 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; private final Thread mThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; Loading @@ -177,7 +178,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, new LinkProperties(), mScore, new NetworkMisc()) { public void unwanted() {} public void unwanted() { mDisconnected.open(); } }; initComplete.open(); Looper.loop(); Loading @@ -197,8 +198,13 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); } /** * Transition this NetworkAgent to CONNECTED state. * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET. * @param validated Indicate if network should pretend to be validated. */ public void connect(boolean validated) { Loading Loading @@ -231,8 +237,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); connectWithoutInternet(); if (validated) { // Wait for network to validate. Loading @@ -252,6 +257,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { public Network getNetwork() { return new Network(mNetworkAgent.netId); } public ConditionVariable getDisconnectedCV() { return mDisconnected; } } private static class MockNetworkFactory extends NetworkFactory { Loading Loading @@ -575,6 +584,34 @@ public class ConnectivityServiceTest extends AndroidTestCase { verifyNoNetwork(); } @LargeTest public void testUnlingeringDoesNotValidate() throws Exception { // Test bringing up unvalidated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test WiFi disconnect. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Unlingering a network should not cause it to be marked as validated. assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); } @LargeTest public void testCellularOutscoresWeakWifi() throws Exception { // Test bringing up validated cellular. Loading Loading @@ -603,6 +640,107 @@ public class ConnectivityServiceTest extends AndroidTestCase { mWiFiNetworkAgent.disconnect(); } @LargeTest public void testReapingNetwork() throws Exception { // Test bringing up WiFi without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up cellular without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular. // Expect it to be torn down because it could never be the highest scoring network // satisfying the default request even if it validated. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.disconnect(); waitFor(cv); } @LargeTest public void testCellularFallback() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Reevaluate WiFi (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); // Should quickly fall back to Cellular. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } @LargeTest public void testWiFiFallback() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } enum CallbackState { NONE, AVAILABLE, Loading Loading @@ -872,6 +1010,71 @@ public class ConnectivityServiceTest extends AndroidTestCase { } catch (IllegalArgumentException expected) {} } @LargeTest public void testMMSonWiFi() throws Exception { // Test bringing up cellular without MMS NetworkRequest gets reaped mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); waitFor(new Criteria() { public boolean get() { return mCm.getAllNetworks().length == 0; } }); verifyNoNetwork(); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); cv = networkCallback.getConditionVariable(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); } @LargeTest public void testMMSonCell() throws Exception { // Test bringing up cellular without MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network cv = networkCallback.getConditionVariable(); MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); waitFor(cv); assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); } // @Override // public void tearDown() throws Exception { // super.tearDown(); Loading Loading
services/core/java/com/android/server/ConnectivityService.java +40 −49 Original line number Diff line number Diff line Loading @@ -2064,12 +2064,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // if it's awaiting captive portal login, or if validation failed), this // may trigger a re-evaluation of the network. private void unlinger(NetworkAgentInfo nai) { nai.networkLingered.clear(); if (!nai.lingering) return; nai.lingering = false; if (VDBG) log("Canceling linger of " + nai.name()); // If network has never been validated, it cannot have been lingered, so don't bother // needlessly triggering a re-evaluation. if (!nai.everValidated) return; nai.networkLingered.clear(); nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); } Loading Loading @@ -2224,23 +2222,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Is nai unneeded by all NetworkRequests (and should be disconnected)? // For validated Networks this is simply whether it is satsifying any NetworkRequests. // For unvalidated Networks this is whether it is satsifying any NetworkRequests or // were it to become validated, would it have a chance of satisfying any NetworkRequests. // This is whether it is satisfying any NetworkRequests or were it to become validated, // would it have a chance of satisfying any NetworkRequests. private boolean unneeded(NetworkAgentInfo nai) { if (!nai.created || nai.isVPN() || nai.lingering) return false; boolean unneeded = true; if (nai.everValidated) { for (int i = 0; i < nai.networkRequests.size() && unneeded; i++) { final NetworkRequest nr = nai.networkRequests.valueAt(i); try { if (isRequest(nr)) unneeded = false; } catch (Exception e) { loge("Request " + nr + " not found in mNetworkRequests."); loge(" it came from request list of " + nai.name()); } } } else { for (NetworkRequestInfo nri : mNetworkRequests.values()) { // If this Network is already the highest scoring Network for a request, or if // there is hope for it to become one if it validated, then it is needed. Loading @@ -2255,12 +2240,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // WiFi ends up validating and out scoring cellular. mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() < nai.getCurrentScoreAsValidated())) { unneeded = false; break; } return false; } } return unneeded; return true; } private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { Loading Loading @@ -4004,29 +3987,29 @@ public class ConnectivityService extends IConnectivityManager.Stub * augmented with any stateful capabilities implied from {@code networkAgent} * (e.g., validated status and captive portal status). * * @param networkAgent the network having its capabilities updated. * @param nai the network having its capabilities updated. * @param networkCapabilities the new network capabilities. */ private void updateCapabilities(NetworkAgentInfo networkAgent, NetworkCapabilities networkCapabilities) { private void updateCapabilities(NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) { // Don't modify caller's NetworkCapabilities. networkCapabilities = new NetworkCapabilities(networkCapabilities); if (networkAgent.lastValidated) { if (nai.lastValidated) { networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); } else { networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED); } if (networkAgent.lastCaptivePortalDetected) { if (nai.lastCaptivePortalDetected) { networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) { synchronized (networkAgent) { networkAgent.networkCapabilities = networkCapabilities; if (!Objects.equals(nai.networkCapabilities, networkCapabilities)) { final int oldScore = nai.getCurrentScore(); synchronized (nai) { nai.networkCapabilities = networkCapabilities; } rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore()); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED); rematchAllNetworksAndRequests(nai, oldScore); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } } Loading Loading @@ -4256,9 +4239,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Linger any networks that are no longer needed. for (NetworkAgentInfo nai : affectedNetworks) { if (nai.everValidated && unneeded(nai)) { if (nai.lingering) { // Already lingered. Nothing to do. This can only happen if "nai" is in // "affectedNetworks" twice. The reasoning being that to get added to // "affectedNetworks", "nai" must have been satisfying a NetworkRequest // (i.e. not lingered) so it could have only been lingered by this loop. // unneeded(nai) will be false and we'll call unlinger() below which would // be bad, so handle it here. } else if (unneeded(nai)) { linger(nai); } else { // Clear nai.networkLingered we might have added above. unlinger(nai); } } Loading Loading @@ -4292,7 +4283,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(), oldDefaultNetwork, true); } mDefaultInetConditionPublished = newNetwork.everValidated ? 100 : 0; mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork); notifyLockdownVpn(newNetwork); } Loading
services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +64 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import android.content.Context; import android.net.LinkProperties; import android.net.Network; Loading @@ -39,6 +41,62 @@ import java.util.Comparator; * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests * interested in using it. Default sort order is descending by score. */ // States of a network: // -------------------- // 1. registered, uncreated, disconnected, unvalidated // This state is entered when a NetworkFactory registers a NetworkAgent in any state except // the CONNECTED state. // 2. registered, uncreated, connected, unvalidated // This state is entered when a registered NetworkAgent transitions to the CONNECTED state // ConnectivityService will tell netd to create the network and immediately transition to // state #3. // 3. registered, created, connected, unvalidated // If this network can satsify the default NetworkRequest, then NetworkMonitor will // probe for Internet connectivity. // If this network cannot satisfy the default NetworkRequest, it will immediately be // transitioned to state #4. // A network may remain in this state if NetworkMonitor fails to find Internet connectivity, // for example: // a. a captive portal is present, or // b. a WiFi router whose Internet backhaul is down, or // c. a wireless connection stops transfering packets temporarily (e.g. device is in elevator // or tunnel) but does not disconnect from the AP/cell tower, or // d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes. // 4. registered, created, connected, validated // // The device's default network connection: // ---------------------------------------- // Networks in states #3 and #4 may be used as a device's default network connection if they // satisfy the default NetworkRequest. // A network, that satisfies the default NetworkRequest, in state #4 should always be chosen // in favor of a network, that satisfies the default NetworkRequest, in state #3. // When deciding between two networks, that both satisfy the default NetworkRequest, to select // for the default network connection, the one with the higher score should be chosen. // // When a network disconnects: // --------------------------- // If a network's transport disappears, for example: // a. WiFi turned off, or // b. cellular data turned off, or // c. airplane mode is turned on, or // d. a wireless connection disconnects from AP/cell tower entirely (e.g. device is out of range // of AP for an extended period of time, or switches to another AP without roaming) // then that network can transition from any state (#1-#4) to unregistered. This happens by // the transport disconnecting their NetworkAgent's AsyncChannel with ConnectivityManager. // ConnectivityService also tells netd to destroy the network. // // When ConnectivityService disconnects a network: // ----------------------------------------------- // If a network has no chance of satisfying any requests (even if it were to become validated // and enter state #4), ConnectivityService will disconnect the NetworkAgent's AsyncChannel. // If the network ever for any period of time had satisfied a NetworkRequest (i.e. had been // the highest scoring that satisfied the NetworkRequest's constraints), but is no longer the // highest scoring network for any NetworkRequest, then there will be a 30s pause before // ConnectivityService disconnects the NetworkAgent's AsyncChannel. During this pause the // network is considered "lingering". This pause exists to allow network communication to be // wrapped up rather than abruptly terminated. During this pause if the network begins satisfying // a NetworkRequest, ConnectivityService will cancel the future disconnection of the NetworkAgent's // AsyncChannel, and the network is no longer considered "lingering". public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkInfo networkInfo; // This Network object should always be used if possible, so as to encourage reuse of the Loading Loading @@ -156,7 +214,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } int score = currentScore; if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY; // Use NET_CAPABILITY_VALIDATED here instead of lastValidated, this allows // ConnectivityService.updateCapabilities() to compute the old score prior to updating // networkCapabilities (with a potentially different validated state). if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) { score -= UNVALIDATED_SCORE_PENALTY; } if (score < 0) score = 0; return score; } Loading
services/core/java/com/android/server/connectivity/NetworkMonitor.java +6 −3 Original line number Diff line number Diff line Loading @@ -585,9 +585,12 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_NETWORK_CONNECTED: log("Unlingered"); // Go straight to active as we've already evaluated. // If already validated, go straight to validated state. if (mNetworkAgentInfo.lastValidated) { transitionTo(mValidatedState); return HANDLED; } return NOT_HANDLED; case CMD_LINGER_EXPIRED: if (message.arg1 != mLingerToken) return HANDLED; Loading
services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +207 −4 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; private final Thread mThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; Loading @@ -177,7 +178,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, new LinkProperties(), mScore, new NetworkMisc()) { public void unwanted() {} public void unwanted() { mDisconnected.open(); } }; initComplete.open(); Looper.loop(); Loading @@ -197,8 +198,13 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); } /** * Transition this NetworkAgent to CONNECTED state. * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET. * @param validated Indicate if network should pretend to be validated. */ public void connect(boolean validated) { Loading Loading @@ -231,8 +237,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); connectWithoutInternet(); if (validated) { // Wait for network to validate. Loading @@ -252,6 +257,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { public Network getNetwork() { return new Network(mNetworkAgent.netId); } public ConditionVariable getDisconnectedCV() { return mDisconnected; } } private static class MockNetworkFactory extends NetworkFactory { Loading Loading @@ -575,6 +584,34 @@ public class ConnectivityServiceTest extends AndroidTestCase { verifyNoNetwork(); } @LargeTest public void testUnlingeringDoesNotValidate() throws Exception { // Test bringing up unvalidated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test WiFi disconnect. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Unlingering a network should not cause it to be marked as validated. assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); } @LargeTest public void testCellularOutscoresWeakWifi() throws Exception { // Test bringing up validated cellular. Loading Loading @@ -603,6 +640,107 @@ public class ConnectivityServiceTest extends AndroidTestCase { mWiFiNetworkAgent.disconnect(); } @LargeTest public void testReapingNetwork() throws Exception { // Test bringing up WiFi without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up cellular without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular. // Expect it to be torn down because it could never be the highest scoring network // satisfying the default request even if it validated. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.disconnect(); waitFor(cv); } @LargeTest public void testCellularFallback() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Reevaluate WiFi (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); // Should quickly fall back to Cellular. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } @LargeTest public void testWiFiFallback() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } enum CallbackState { NONE, AVAILABLE, Loading Loading @@ -872,6 +1010,71 @@ public class ConnectivityServiceTest extends AndroidTestCase { } catch (IllegalArgumentException expected) {} } @LargeTest public void testMMSonWiFi() throws Exception { // Test bringing up cellular without MMS NetworkRequest gets reaped mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); waitFor(new Criteria() { public boolean get() { return mCm.getAllNetworks().length == 0; } }); verifyNoNetwork(); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); cv = networkCallback.getConditionVariable(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); } @LargeTest public void testMMSonCell() throws Exception { // Test bringing up cellular without MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network cv = networkCallback.getConditionVariable(); MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); waitFor(cv); assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); } // @Override // public void tearDown() throws Exception { // super.tearDown(); Loading