diff options
author | Paul Jensen <pauljensen@google.com> | 2015-06-25 15:30:08 -0400 |
---|---|---|
committer | Paul Jensen <pauljensen@google.com> | 2015-07-09 10:26:17 -0400 |
commit | e098854c41a72b22f4174bc623e8e93cde8d7331 (patch) | |
tree | 0c4c735e3f35fcdf50114ef726e1cf1eb325663b | |
parent | 85cf78edc92b85ec90e91de42b14b84e202260f3 (diff) | |
download | frameworks_base-e098854c41a72b22f4174bc623e8e93cde8d7331.zip frameworks_base-e098854c41a72b22f4174bc623e8e93cde8d7331.tar.gz frameworks_base-e098854c41a72b22f4174bc623e8e93cde8d7331.tar.bz2 |
Fallback to Cellular if WiFi fails to validate
Previously, once a network validated, for the purposes of comparing networks
to select the default network, we always considered it validated.
With this change if a network later fails to validate, we'll take this latest
validation result into account. This means if WiFi and cellular are up
(e.g. if we recently switched from cellular->WiFi, and cellular is now
lingering) and both are validated, but for some reason WiFi fails a validation,
cellular will become the default network connection.
Bug:20896761
Change-Id: I858aa10c1aaec5cd9032067f960963409107bdb1
4 files changed, 317 insertions, 57 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6878caf..d95b233 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2047,12 +2047,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); } @@ -2207,43 +2205,28 @@ 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. - if (nri.isRequest && nai.satisfies(nri.request) && - (nai.networkRequests.get(nri.request.requestId) != null || - // Note that this catches two important cases: - // 1. Unvalidated cellular will not be reaped when unvalidated WiFi - // is currently satisfying the request. This is desirable when - // cellular ends up validating but WiFi does not. - // 2. Unvalidated WiFi will not be reaped when validated cellular - // is currently satsifying the request. This is desirable when - // WiFi ends up validating and out scoring cellular. - mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() < - nai.getCurrentScoreAsValidated())) { - unneeded = false; - break; - } + 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. + if (nri.isRequest && nai.satisfies(nri.request) && + (nai.networkRequests.get(nri.request.requestId) != null || + // Note that this catches two important cases: + // 1. Unvalidated cellular will not be reaped when unvalidated WiFi + // is currently satisfying the request. This is desirable when + // cellular ends up validating but WiFi does not. + // 2. Unvalidated WiFi will not be reaped when validated cellular + // is currently satsifying the request. This is desirable when + // WiFi ends up validating and out scoring cellular. + mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() < + nai.getCurrentScoreAsValidated())) { + return false; } } - return unneeded; + return true; } private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { @@ -3997,29 +3980,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); } } @@ -4249,9 +4232,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); } } @@ -4285,7 +4276,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); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 0a0c096..8a79430 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -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; @@ -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 @@ -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; } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index e472928..5e098d4 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -576,9 +576,12 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_NETWORK_CONNECTED: log("Unlingered"); - // Go straight to active as we've already evaluated. - transitionTo(mValidatedState); - return HANDLED; + // 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; diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index c1311ed..cb9c6a7 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -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; @@ -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(); @@ -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) { @@ -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. @@ -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 { @@ -576,6 +585,34 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @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. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); @@ -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, @@ -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(); |