Merge "Legacy VPN calls to require a userID" into mnc-dev
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
new file mode 100644
index 0000000..ee05f28
--- /dev/null
+++ b/core/java/android/net/CaptivePortal.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed urnder 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 android.net;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
+ * activity to indicate to the system different outcomes of captive portal sign in. This class is
+ * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the
+ * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
+ */
+public class CaptivePortal implements Parcelable {
+ /** @hide */
+ public static final int APP_RETURN_DISMISSED = 0;
+ /** @hide */
+ public static final int APP_RETURN_UNWANTED = 1;
+ /** @hide */
+ public static final int APP_RETURN_WANTED_AS_IS = 2;
+
+ private final IBinder mBinder;
+
+ /** @hide */
+ public CaptivePortal(IBinder binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mBinder);
+ }
+
+ public static final Parcelable.Creator<CaptivePortal> CREATOR
+ = new Parcelable.Creator<CaptivePortal>() {
+ @Override
+ public CaptivePortal createFromParcel(Parcel in) {
+ return new CaptivePortal(in.readStrongBinder());
+ }
+
+ @Override
+ public CaptivePortal[] newArray(int size) {
+ return new CaptivePortal[size];
+ }
+ };
+
+ /**
+ * Indicate to the system that the captive portal has been
+ * dismissed. In response the framework will re-evaluate the network's
+ * connectivity and might take further action thereafter.
+ */
+ public void reportCaptivePortalDismissed() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system that the user does not want to pursue signing in to the
+ * captive portal and the system should continue to prefer other networks
+ * without captive portals for use as the default active data network. The
+ * system will not retest the network for a captive portal so as to avoid
+ * disturbing the user with further sign in to network notifications.
+ */
+ public void ignoreNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system the user wants to use this network as is, even though
+ * the captive portal is still in place. The system will treat the network
+ * as if it did not have a captive portal when selecting the network to use
+ * as the default active data network. This may result in this network
+ * becoming the default active data network, which could disrupt network
+ * connectivity for apps because the captive portal is still in place.
+ * @hide
+ */
+ public void useNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 12cd5f1..dc8ff8f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -102,26 +102,26 @@
* portal, which is blocking Internet connectivity. The user was presented
* with a notification that network sign in is required,
* and the user invoked the notification's action indicating they
- * desire to sign in to the network. Apps handling this action should
+ * desire to sign in to the network. Apps handling this activity should
* facilitate signing in to the network. This action includes a
* {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents
* the network presenting the captive portal; all communication with the
* captive portal must be done using this {@code Network} object.
* <p/>
- * When the app handling this action believes the user has signed in to
- * the network and the captive portal has been dismissed, the app should call
- * {@link #reportCaptivePortalDismissed} so the system can reevaluate the network.
- * If reevaluation finds the network no longer subject to a captive portal,
- * the network may become the default active data network.
- * <p/>
- * When the app handling this action believes the user explicitly wants
+ * This activity includes a {@link CaptivePortal} extra named
+ * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different
+ * outcomes of the captive portal sign in to the system:
+ * <ul>
+ * <li> When the app handling this action believes the user has signed in to
+ * the network and the captive portal has been dismissed, the app should
+ * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
+ * reevaluate the network. If reevaluation finds the network no longer
+ * subject to a captive portal, the network may become the default active
+ * data network. </li>
+ * <li> When the app handling this action believes the user explicitly wants
* to ignore the captive portal and the network, the app should call
- * {@link #ignoreNetworkWithCaptivePortal}.
- * <p/>
- * Note that this action includes a {@code String} extra named
- * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} that must
- * be passed in to {@link #reportCaptivePortalDismissed} and
- * {@link #ignoreNetworkWithCaptivePortal}.
+ * {@link CaptivePortal#ignoreNetwork}. </li>
+ * </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
@@ -187,16 +187,15 @@
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
-
/**
- * The lookup key for a string that is sent out with
- * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}. This string must be
- * passed in to {@link #reportCaptivePortalDismissed} and
- * {@link #ignoreNetworkWithCaptivePortal}. Retrieve it with
- * {@link android.content.Intent#getStringExtra(String)}.
+ * The lookup key for a {@link CaptivePortal} object included with the
+ * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal}
+ * object can be used to either indicate to the system that the captive
+ * portal has been dismissed or that the user does not want to pursue
+ * signing in to captive portal. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
*/
- public static final String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
-
+ public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
/**
* Broadcast action to indicate the change of data activity status
* (idle or active) on a network in a recent period.
@@ -432,7 +431,8 @@
public static final int TYPE_MOBILE_IA = 14;
/**
- * Emergency PDN connection for emergency calls
+ * Emergency PDN connection for emergency services. This
+ * may include IMS and MMS in emergency situations.
* {@hide}
*/
public static final int TYPE_MOBILE_EMERGENCY = 15;
@@ -1779,82 +1779,6 @@
}
}
- /** {@hide} */
- public static final int CAPTIVE_PORTAL_APP_RETURN_DISMISSED = 0;
- /** {@hide} */
- public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1;
- /** {@hide} */
- public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2;
-
- /**
- * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
- * action to indicate to the system that the captive portal has been
- * dismissed. In response the framework will re-evaluate the network's
- * connectivity and might take further action thereafter.
- *
- * @param network The {@link Network} object passed via
- * {@link #EXTRA_NETWORK} with the
- * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- * @param actionToken The {@code String} passed via
- * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
- * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- */
- public void reportCaptivePortalDismissed(Network network, String actionToken) {
- try {
- mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_DISMISSED,
- actionToken);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
- * action to indicate that the user does not want to pursue signing in to
- * captive portal and the system should continue to prefer other networks
- * without captive portals for use as the default active data network. The
- * system will not retest the network for a captive portal so as to avoid
- * disturbing the user with further sign in to network notifications.
- *
- * @param network The {@link Network} object passed via
- * {@link #EXTRA_NETWORK} with the
- * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- * @param actionToken The {@code String} passed via
- * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
- * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- */
- public void ignoreNetworkWithCaptivePortal(Network network, String actionToken) {
- try {
- mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
- actionToken);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
- * action to indicate the user wants to use this network as is, even though
- * the captive portal is still in place. The system will treat the network
- * as if it did not have a captive portal when selecting the network to use
- * as the default active data network. This may result in this network
- * becoming the default active data network, which could disrupt network
- * connectivity for apps because the captive portal is still in place.
- *
- * @param network The {@link Network} object passed via
- * {@link #EXTRA_NETWORK} with the
- * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- * @param actionToken The {@code String} passed via
- * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
- * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
- * @hide
- */
- public void useNetworkWithCaptivePortal(Network network, String actionToken) {
- try {
- mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS,
- actionToken);
- } catch (RemoteException e) {
- }
- }
-
/**
* Set a network-independent global http proxy. This is not normally what you want
* for typical HTTP proxies - they are general network dependent. However if you're
@@ -2081,23 +2005,6 @@
* changes. Should be extended by applications wanting notifications.
*/
public static class NetworkCallback {
- /** @hide */
- public static final int PRECHECK = 1;
- /** @hide */
- public static final int AVAILABLE = 2;
- /** @hide */
- public static final int LOSING = 3;
- /** @hide */
- public static final int LOST = 4;
- /** @hide */
- public static final int UNAVAIL = 5;
- /** @hide */
- public static final int CAP_CHANGED = 6;
- /** @hide */
- public static final int PROP_CHANGED = 7;
- /** @hide */
- public static final int CANCELED = 8;
-
/**
* Called when the framework connects to a new network to evaluate whether it satisfies this
* request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable}
@@ -2174,30 +2081,54 @@
*/
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {}
+ /**
+ * Called when the network the framework connected to for this request
+ * goes into {@link NetworkInfo.DetailedState.SUSPENDED}.
+ * This generally means that while the TCP connections are still live,
+ * temporarily network data fails to transfer. Specifically this is used
+ * on cellular networks to mask temporary outages when driving through
+ * a tunnel, etc.
+ * @hide
+ */
+ public void onNetworkSuspended(Network network) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * returns from a {@link NetworkInfo.DetailedState.SUSPENDED} state.
+ * This should always be preceeded by a matching {@code onNetworkSuspended}
+ * call.
+ * @hide
+ */
+ public void onNetworkResumed(Network network) {}
+
private NetworkRequest networkRequest;
}
private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_PRECHECK = BASE + 1;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_AVAILABLE = BASE + 2;
- /** @hide obj = pair(NetworkRequest, Network), arg1 = ttl */
- public static final int CALLBACK_LOSING = BASE + 3;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_LOST = BASE + 4;
- /** @hide obj = NetworkRequest */
- public static final int CALLBACK_UNAVAIL = BASE + 5;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_CAP_CHANGED = BASE + 6;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_IP_CHANGED = BASE + 7;
- /** @hide obj = NetworkRequest */
- public static final int CALLBACK_RELEASED = BASE + 8;
/** @hide */
- public static final int CALLBACK_EXIT = BASE + 9;
+ public static final int CALLBACK_PRECHECK = BASE + 1;
+ /** @hide */
+ public static final int CALLBACK_AVAILABLE = BASE + 2;
+ /** @hide arg1 = TTL */
+ public static final int CALLBACK_LOSING = BASE + 3;
+ /** @hide */
+ public static final int CALLBACK_LOST = BASE + 4;
+ /** @hide */
+ public static final int CALLBACK_UNAVAIL = BASE + 5;
+ /** @hide */
+ public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ /** @hide */
+ public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ /** @hide */
+ public static final int CALLBACK_RELEASED = BASE + 8;
+ /** @hide */
+ public static final int CALLBACK_EXIT = BASE + 9;
/** @hide obj = NetworkCapabilities, arg1 = seq number */
- private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+ /** @hide */
+ public static final int CALLBACK_SUSPENDED = BASE + 11;
+ /** @hide */
+ public static final int CALLBACK_RESUMED = BASE + 12;
private class CallbackHandler extends Handler {
private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
@@ -2274,6 +2205,20 @@
}
break;
}
+ case CALLBACK_SUSPENDED: {
+ NetworkCallback callback = getCallback(request, "SUSPENDED");
+ if (callback != null) {
+ callback.onNetworkSuspended(network);
+ }
+ break;
+ }
+ case CALLBACK_RESUMED: {
+ NetworkCallback callback = getCallback(request, "RESUMED");
+ if (callback != null) {
+ callback.onNetworkResumed(network);
+ }
+ break;
+ }
case CALLBACK_RELEASED: {
NetworkCallback callback = null;
synchronized(mCallbackMap) {
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 58035a2..46c28a6 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -96,8 +96,6 @@
void reportNetworkConnectivity(in Network network, boolean hasConnectivity);
- void captivePortalAppResponse(in Network network, int response, String actionToken);
-
ProxyInfo getGlobalProxy();
void setGlobalProxy(in ProxyInfo p);
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 658051c..514d24a 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -133,7 +133,8 @@
/**
* Indicates this is a network that has the ability to reach a carrier's
- * Emergency IMS servers, used for network signaling during emergency calls.
+ * Emergency IMS servers or other services, used for network signaling
+ * during emergency calls.
*/
public static final int NET_CAPABILITY_EIMS = 10;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5044ba9..39c2891 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -220,23 +220,13 @@
private static final int ENABLED = 1;
private static final int DISABLED = 0;
- // Arguments to rematchNetworkAndRequests()
- private enum NascentState {
- // Indicates a network was just validated for the first time. If the network is found to
- // be unwanted (i.e. not satisfy any NetworkRequests) it is torn down.
- JUST_VALIDATED,
- // Indicates a network was not validated for the first time immediately prior to this call.
- NOT_JUST_VALIDATED
- };
private enum ReapUnvalidatedNetworks {
- // Tear down unvalidated networks that have no chance (i.e. even if validated) of becoming
- // the highest scoring network satisfying a NetworkRequest. This should be passed when it's
- // known that there may be unvalidated networks that could potentially be reaped, and when
+ // Tear down networks that have no chance (e.g. even if validated) of becoming
+ // the highest scoring network satisfying a NetworkRequest. This should be passed when
// all networks have been rematched against all NetworkRequests.
REAP,
- // Don't reap unvalidated networks. This should be passed when it's known that there are
- // no unvalidated networks that could potentially be reaped, and when some networks have
- // not yet been rematched against all NetworkRequests.
+ // Don't reap networks. This should be passed when some networks have not yet been
+ // rematched against all NetworkRequests.
DONT_REAP
};
@@ -493,10 +483,10 @@
}
}
- private void maybeLogBroadcast(NetworkAgentInfo nai, boolean connected, int type,
+ private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
boolean isDefaultNetwork) {
if (DBG) {
- log("Sending " + (connected ? "connected" : "disconnected") +
+ log("Sending " + state +
" broadcast for type " + type + " " + nai.name() +
" isDefaultNetwork=" + isDefaultNetwork);
}
@@ -520,8 +510,8 @@
// Send a broadcast if this is the first network of its type or if it's the default.
final boolean isDefaultNetwork = isDefaultNetwork(nai);
if (list.size() == 1 || isDefaultNetwork) {
- maybeLogBroadcast(nai, true, type, isDefaultNetwork);
- sendLegacyNetworkBroadcast(nai, true, type);
+ maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
+ sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
}
}
@@ -538,17 +528,19 @@
return;
}
+ final DetailedState state = DetailedState.DISCONNECTED;
+
if (wasFirstNetwork || wasDefault) {
- maybeLogBroadcast(nai, false, type, wasDefault);
- sendLegacyNetworkBroadcast(nai, false, type);
+ maybeLogBroadcast(nai, state, type, wasDefault);
+ sendLegacyNetworkBroadcast(nai, state, type);
}
if (!list.isEmpty() && wasFirstNetwork) {
if (DBG) log("Other network available for type " + type +
", sending connected broadcast");
final NetworkAgentInfo replacement = list.get(0);
- maybeLogBroadcast(replacement, false, type, isDefaultNetwork(replacement));
- sendLegacyNetworkBroadcast(replacement, false, type);
+ maybeLogBroadcast(replacement, state, type, isDefaultNetwork(replacement));
+ sendLegacyNetworkBroadcast(replacement, state, type);
}
}
@@ -560,6 +552,21 @@
}
}
+ // send out another legacy broadcast - currently only used for suspend/unsuspend
+ // toggle
+ public void update(NetworkAgentInfo nai) {
+ final boolean isDefault = isDefaultNetwork(nai);
+ final DetailedState state = nai.networkInfo.getDetailedState();
+ for (int type = 0; type < mTypeLists.length; type++) {
+ final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
+ final boolean isFirst = (list != null && list.size() > 0 && nai == list.get(0));
+ if (isFirst || isDefault) {
+ maybeLogBroadcast(nai, state, type, isDefault);
+ sendLegacyNetworkBroadcast(nai, state, type);
+ }
+ }
+ }
+
private String naiToString(NetworkAgentInfo nai) {
String name = (nai != null) ? nai.name() : "null";
String state = (nai.networkInfo != null) ?
@@ -873,7 +880,7 @@
Network network = null;
String subscriberId = null;
- NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ NetworkAgentInfo nai = getDefaultNetwork();
final Network[] networks = getVpnUnderlyingNetworks(uid);
if (networks != null) {
@@ -1778,7 +1785,7 @@
pw.println();
pw.println();
- NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
pw.print("Active default network: ");
if (defaultNai == null) {
pw.println("none");
@@ -1903,8 +1910,7 @@
networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
Slog.wtf(TAG, "BUG: " + nai + " has stateful capability.");
}
- updateCapabilities(nai, networkCapabilities,
- NascentState.NOT_JUST_VALIDATED);
+ updateCapabilities(nai, networkCapabilities);
}
break;
}
@@ -1994,11 +2000,9 @@
if (DBG) log(nai.name() + " validation " + (valid ? " passed" : "failed"));
if (valid != nai.lastValidated) {
final int oldScore = nai.getCurrentScore();
- final NascentState nascent = (valid && !nai.everValidated) ?
- NascentState.JUST_VALIDATED : NascentState.NOT_JUST_VALIDATED;
nai.lastValidated = valid;
nai.everValidated |= valid;
- updateCapabilities(nai, nai.networkCapabilities, nascent);
+ updateCapabilities(nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}
@@ -2029,8 +2033,7 @@
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
- updateCapabilities(nai, nai.networkCapabilities,
- NascentState.NOT_JUST_VALIDATED);
+ updateCapabilities(nai, nai.networkCapabilities);
}
if (!visible) {
setProvNotificationVisibleIntent(false, netId, null, 0, null, null, false);
@@ -2049,17 +2052,22 @@
}
}
+ private void linger(NetworkAgentInfo nai) {
+ nai.lingering = true;
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ }
+
// Cancel any lingering so the linger timeout doesn't teardown a network.
// This should be called when a network begins satisfying a NetworkRequest.
// Note: depending on what state the NetworkMonitor is in (e.g.,
// 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) {
- 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();
+ if (!nai.lingering) return;
+ nai.lingering = false;
+ if (VDBG) log("Canceling linger of " + nai.name());
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
}
@@ -2130,33 +2138,13 @@
// available until we've told netd to delete it below.
mNetworkForNetId.remove(nai.network.netId);
}
- // Since we've lost the network, go through all the requests that
- // it was satisfying and see if any other factory can satisfy them.
- // TODO: This logic may be better replaced with a call to rematchAllNetworksAndRequests
- final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
+ // Remove all previously satisfied requests.
for (int i = 0; i < nai.networkRequests.size(); i++) {
NetworkRequest request = nai.networkRequests.valueAt(i);
NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
- if (DBG) {
- log("Checking for replacement network to handle request " + request );
- }
mNetworkForRequestId.remove(request.requestId);
sendUpdatedScoreToFactories(request, 0);
- NetworkAgentInfo alternative = null;
- for (NetworkAgentInfo existing : mNetworkAgentInfos.values()) {
- if (existing.satisfies(request) &&
- (alternative == null ||
- alternative.getCurrentScore() < existing.getCurrentScore())) {
- alternative = existing;
- }
- }
- if (alternative != null) {
- if (DBG) log(" found replacement in " + alternative.name());
- if (!toActivate.contains(alternative)) {
- toActivate.add(alternative);
- }
- }
}
}
if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
@@ -2165,11 +2153,7 @@
requestNetworkTransitionWakelock(nai.name());
}
mLegacyTypeTracker.remove(nai, wasDefault);
- for (NetworkAgentInfo networkToActivate : toActivate) {
- unlinger(networkToActivate);
- rematchNetworkAndRequests(networkToActivate, NascentState.NOT_JUST_VALIDATED,
- ReapUnvalidatedNetworks.DONT_REAP);
- }
+ rematchAllNetworksAndRequests(null, 0);
if (nai.created) {
// Tell netd to clean up the configuration for this network
// (routing rules, DNS, etc).
@@ -2223,49 +2207,9 @@
private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
mNetworkRequests.put(nri.request, nri);
-
- // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
-
- // Check for the best currently alive network that satisfies this request
- NetworkAgentInfo bestNetwork = null;
- for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
- if (DBG) log("handleRegisterNetworkRequest checking " + network.name());
- if (network.satisfies(nri.request)) {
- if (DBG) log("apparently satisfied. currentScore=" + network.getCurrentScore());
- if (!nri.isRequest) {
- // Not setting bestNetwork here as a listening NetworkRequest may be
- // satisfied by multiple Networks. Instead the request is added to
- // each satisfying Network and notified about each.
- if (!network.addRequest(nri.request)) {
- Slog.wtf(TAG, "BUG: " + network.name() + " already has " + nri.request);
- }
- notifyNetworkCallback(network, nri);
- } else if (bestNetwork == null ||
- bestNetwork.getCurrentScore() < network.getCurrentScore()) {
- bestNetwork = network;
- }
- }
- }
- if (bestNetwork != null) {
- if (DBG) log("using " + bestNetwork.name());
- unlinger(bestNetwork);
- if (!bestNetwork.addRequest(nri.request)) {
- Slog.wtf(TAG, "BUG: " + bestNetwork.name() + " already has " + nri.request);
- }
- mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
- notifyNetworkCallback(bestNetwork, nri);
- if (nri.request.legacyType != TYPE_NONE) {
- mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
- }
- }
-
- if (nri.isRequest) {
- if (DBG) log("sending new NetworkRequest to factories");
- final int score = bestNetwork == null ? 0 : bestNetwork.getCurrentScore();
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
- 0, nri.request);
- }
+ rematchAllNetworksAndRequests(null, 0);
+ if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
+ sendUpdatedScoreToFactories(nri.request, 0);
}
}
@@ -2278,43 +2222,28 @@
}
// 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()) 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;
- }
+ if (!nai.created || nai.isVPN() || nai.lingering) return false;
+ 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) {
@@ -2424,7 +2353,7 @@
if (accept != nai.networkMisc.acceptUnvalidated) {
int oldScore = nai.getCurrentScore();
nai.networkMisc.acceptUnvalidated = accept;
- rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
+ rematchAllNetworksAndRequests(nai, oldScore);
sendUpdatedScoreToFactories(nai);
}
@@ -2733,16 +2662,6 @@
}
}
- public void captivePortalAppResponse(Network network, int response, String actionToken) {
- if (response == ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS) {
- enforceConnectivityInternalPermission();
- }
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai == null) return;
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_CAPTIVE_PORTAL_APP_FINISHED, response, 0,
- actionToken);
- }
-
private ProxyInfo getDefaultProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
@@ -4033,7 +3952,7 @@
} catch (Exception e) {
loge("Exception in setDnsServersForNetwork: " + e);
}
- NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
if (defaultNai != null && defaultNai.network.netId == netId) {
setDefaultDnsSystemProperties(dnses);
}
@@ -4068,31 +3987,29 @@
* 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.
- * @param nascent indicates whether {@code networkAgent} was validated
- * (i.e. had everValidated set for the first time) immediately prior to this call.
*/
- private void updateCapabilities(NetworkAgentInfo networkAgent,
- NetworkCapabilities networkCapabilities, NascentState nascent) {
+ 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(), nascent);
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ rematchAllNetworksAndRequests(nai, oldScore);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
}
@@ -4234,7 +4151,7 @@
// one or more NetworkRequests, or if it is a VPN.
//
// - Tears down newNetwork if it just became validated
- // (i.e. nascent==JUST_VALIDATED) but turns out to be unneeded.
+ // but turns out to be unneeded.
//
// - If reapUnvalidatedNetworks==REAP, tears down unvalidated
// networks that have no chance (i.e. even if validated)
@@ -4247,17 +4164,12 @@
// as it performs better by a factor of the number of Networks.
//
// @param newNetwork is the network to be matched against NetworkRequests.
- // @param nascent indicates if newNetwork just became validated, in which case it should be
- // torn down if unneeded.
// @param reapUnvalidatedNetworks indicates if an additional pass over all networks should be
// performed to tear down unvalidated networks that have no chance (i.e. even if
// validated) of becoming the highest scoring network.
- private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, NascentState nascent,
+ private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,
ReapUnvalidatedNetworks reapUnvalidatedNetworks) {
if (!newNetwork.created) return;
- if (nascent == NascentState.JUST_VALIDATED && !newNetwork.everValidated) {
- loge("ERROR: nascent network not validated.");
- }
boolean keep = newNetwork.isVPN();
boolean isNewDefault = false;
NetworkAgentInfo oldDefaultNetwork = null;
@@ -4270,7 +4182,7 @@
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
if (newNetwork == currentNetwork) {
- if (DBG) {
+ if (VDBG) {
log("Network " + newNetwork.name() + " was already satisfying" +
" request " + nri.request.requestId + ". No change.");
}
@@ -4327,10 +4239,17 @@
}
// Linger any networks that are no longer needed.
for (NetworkAgentInfo nai : affectedNetworks) {
- if (nai.everValidated && unneeded(nai)) {
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ 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);
}
}
@@ -4364,7 +4283,7 @@
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);
}
@@ -4415,19 +4334,10 @@
if (newNetwork.isVPN()) {
mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
}
- } else if (nascent == NascentState.JUST_VALIDATED) {
- // Only tear down newly validated networks here. Leave unvalidated to either become
- // validated (and get evaluated against peers, one losing here), or get reaped (see
- // reapUnvalidatedNetworks) if they have no chance of becoming the highest scoring
- // network. Networks that have been up for a while and are validated should be torn
- // down via the lingering process so communication on that network is given time to
- // wrap up.
- if (DBG) log("Validated network turns out to be unwanted. Tear it down.");
- teardownUnneededNetwork(newNetwork);
}
if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- if (!nai.everValidated && unneeded(nai)) {
+ if (unneeded(nai)) {
if (DBG) log("Reaping " + nai.name());
teardownUnneededNetwork(nai);
}
@@ -4446,10 +4356,8 @@
* this argument, otherwise pass {@code changed.getCurrentScore()} or 0 if
* {@code changed} is {@code null}. This is because NetworkCapabilities influence a
* network's score.
- * @param nascent indicates if {@code changed} has just been validated.
*/
- private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore,
- NascentState nascent) {
+ private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {
// TODO: This may get slow. The "changed" parameter is provided for future optimization
// to avoid the slowness. It is not simply enough to process just "changed", for
// example in the case where "changed"'s score decreases and another network should begin
@@ -4458,17 +4366,21 @@
// Optimization: Only reprocess "changed" if its score improved. This is safe because it
// can only add more NetworkRequests satisfied by "changed", and this is exactly what
// rematchNetworkAndRequests() handles.
- if (changed != null &&
- (oldScore < changed.getCurrentScore() || nascent == NascentState.JUST_VALIDATED)) {
- rematchNetworkAndRequests(changed, nascent, ReapUnvalidatedNetworks.REAP);
+ if (changed != null && oldScore < changed.getCurrentScore()) {
+ rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP);
} else {
- for (Iterator i = mNetworkAgentInfos.values().iterator(); i.hasNext(); ) {
- rematchNetworkAndRequests((NetworkAgentInfo)i.next(),
- NascentState.NOT_JUST_VALIDATED,
+ final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
+ new NetworkAgentInfo[mNetworkAgentInfos.size()]);
+ // Rematch higher scoring networks first to prevent requests first matching a lower
+ // scoring network and then a higher scoring network, which could produce multiple
+ // callbacks and inadvertently unlinger networks.
+ Arrays.sort(nais);
+ for (NetworkAgentInfo nai : nais) {
+ rematchNetworkAndRequests(nai,
// Only reap the last time through the loop. Reaping before all rematching
// is complete could incorrectly teardown a network that hasn't yet been
// rematched.
- i.hasNext() ? ReapUnvalidatedNetworks.DONT_REAP
+ (nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP
: ReapUnvalidatedNetworks.REAP);
}
}
@@ -4502,6 +4414,7 @@
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
NetworkInfo.State state = newInfo.getState();
NetworkInfo oldInfo = null;
+ final int oldScore = networkAgent.getCurrentScore();
synchronized (networkAgent) {
oldInfo = networkAgent.networkInfo;
networkAgent.networkInfo = newInfo;
@@ -4555,13 +4468,11 @@
}
// Consider network even though it is not yet validated.
- rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
- ReapUnvalidatedNetworks.REAP);
+ rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP);
// This has to happen after matching the requests, because callbacks are just requests.
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
- } else if (state == NetworkInfo.State.DISCONNECTED ||
- state == NetworkInfo.State.SUSPENDED) {
+ } else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
synchronized (mProxyLock) {
@@ -4573,6 +4484,16 @@
}
}
}
+ } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
+ state == NetworkInfo.State.SUSPENDED) {
+ // going into or coming out of SUSPEND: rescore and notify
+ if (networkAgent.getCurrentScore() != oldScore) {
+ rematchAllNetworksAndRequests(networkAgent, oldScore);
+ }
+ notifyNetworkCallbacks(networkAgent, (state == NetworkInfo.State.SUSPENDED ?
+ ConnectivityManager.CALLBACK_SUSPENDED :
+ ConnectivityManager.CALLBACK_RESUMED));
+ mLegacyTypeTracker.update(networkAgent);
}
}
@@ -4587,7 +4508,7 @@
final int oldScore = nai.getCurrentScore();
nai.setCurrentScore(score);
- rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
+ rematchAllNetworksAndRequests(nai, oldScore);
sendUpdatedScoreToFactories(nai);
}
@@ -4608,7 +4529,7 @@
}
}
- private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+ private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
// The NetworkInfo we actually send out has no bearing on the real
// state of affairs. For example, if the default connection is mobile,
// and a request for HIPRI has just gone away, we need to pretend that
@@ -4617,11 +4538,11 @@
// and is still connected.
NetworkInfo info = new NetworkInfo(nai.networkInfo);
info.setType(type);
- if (connected) {
- info.setDetailedState(DetailedState.CONNECTED, null, info.getExtraInfo());
+ if (state != DetailedState.DISCONNECTED) {
+ info.setDetailedState(state, null, info.getExtraInfo());
sendConnectedBroadcast(info);
} else {
- info.setDetailedState(DetailedState.DISCONNECTED, info.getReason(), info.getExtraInfo());
+ info.setDetailedState(state, info.getReason(), info.getExtraInfo());
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -4637,7 +4558,7 @@
}
NetworkAgentInfo newDefaultAgent = null;
if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
- newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ newDefaultAgent = getDefaultNetwork();
if (newDefaultAgent != null) {
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
newDefaultAgent.networkInfo);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 51c6628..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;
@@ -31,14 +33,71 @@
import com.android.server.connectivity.NetworkMonitor;
import java.util.ArrayList;
+import java.util.Comparator;
/**
* A bag class used by ConnectivityService for holding a collection of most recent
* information published by a particular NetworkAgent as well as the
* AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
- * interested in using it.
+ * interested in using it. Default sort order is descending by score.
*/
-public class NetworkAgentInfo {
+// 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
// enclosed socket factory and connection pool. Avoid creating other Network objects.
@@ -72,6 +131,13 @@
// Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected;
+ // Indicates whether the network is lingering. Networks are lingered when they become unneeded
+ // as a result of their NetworkRequests being satisfied by a different 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 either the linger timeout expiring and the network
+ // being taken down, or the network satisfying a request again.
+ public boolean lingering;
+
// This represents the last score received from the NetworkAgent.
private int currentScore;
// Penalty applied to scores of Networks that have not been validated.
@@ -148,7 +214,12 @@
}
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;
}
@@ -175,7 +246,7 @@
linkProperties + "} nc{" +
networkCapabilities + "} Score{" + getCurrentScore() + "} " +
"everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} " +
+ "created{" + created + "} lingering{" + lingering + "} " +
"explicitlySelected{" + networkMisc.explicitlySelected + "} " +
"acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
"everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
@@ -188,4 +259,10 @@
networkInfo.getSubtypeName() + ") - " +
(network == null ? "null" : network.toString()) + "]";
}
+
+ // Enables sorting in descending order of score.
+ @Override
+ public int compareTo(NetworkAgentInfo other) {
+ return other.getCurrentScore() - getCurrentScore();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index fb8a5bb..cb9c6a7 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -20,8 +20,24 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static org.mockito.Matchers.anyInt;
@@ -135,6 +151,7 @@
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
private final Thread mThread;
+ private final ConditionVariable mDisconnected = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
@@ -161,7 +178,7 @@
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();
@@ -176,8 +193,18 @@
mNetworkAgent.sendNetworkScore(mScore);
}
+ public void addCapability(int capability) {
+ mNetworkCapabilities.addCapability(capability);
+ 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) {
@@ -210,8 +237,7 @@
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
- mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ connectWithoutInternet();
if (validated) {
// Wait for network to validate.
@@ -231,6 +257,10 @@
public Network getNetwork() {
return new Network(mNetworkAgent.netId);
}
+
+ public ConditionVariable getDisconnectedCV() {
+ return mDisconnected;
+ }
}
private static class MockNetworkFactory extends NetworkFactory {
@@ -555,6 +585,34 @@
}
@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);
@@ -582,6 +640,107 @@
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,
@@ -736,10 +895,9 @@
assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
}
- @LargeTest
- public void testNetworkFactoryRequests() throws Exception {
+ private void tryNetworkFactoryRequests(int capability) throws Exception {
NetworkCapabilities filter = new NetworkCapabilities();
- filter.addCapability(NET_CAPABILITY_INTERNET);
+ filter.addCapability(capability);
final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
handlerThread.start();
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -747,57 +905,91 @@
testFactory.setScoreFilter(40);
ConditionVariable cv = testFactory.getNetworkStartedCV();
testFactory.register();
+ int expectedRequestCount = 1;
+ NetworkCallback networkCallback = null;
+ // For non-INTERNET capabilities we cannot rely on the default request being present, so
+ // add one.
+ if (capability != NET_CAPABILITY_INTERNET) {
+ testFactory.waitForNetworkRequests(1);
+ assertFalse(testFactory.getMyStartRequested());
+ NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
+ networkCallback = new NetworkCallback();
+ mCm.requestNetwork(request, networkCallback);
+ expectedRequestCount++;
+ }
waitFor(cv);
- assertEquals(1, testFactory.getMyRequestCount());
- assertEquals(true, testFactory.getMyStartRequested());
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertTrue(testFactory.getMyStartRequested());
- // now bring in a higher scored network
+ // Now bring in a higher scored network.
MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
- cv = waitForConnectivityBroadcasts(1);
- ConditionVariable cvRelease = testFactory.getNetworkStoppedCV();
- testAgent.connect(true);
+ // Rather than create a validated network which complicates things by registering it's
+ // own NetworkRequest during startup, just bump up the score to cancel out the
+ // unvalidated penalty.
+ testAgent.adjustScore(40);
+ cv = testFactory.getNetworkStoppedCV();
+ testAgent.connect(false);
+ testAgent.addCapability(capability);
waitFor(cv);
- // part of the bringup makes another network request and then releases it
- // wait for the release
- waitFor(cvRelease);
- assertEquals(false, testFactory.getMyStartRequested());
- testFactory.waitForNetworkRequests(1);
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertFalse(testFactory.getMyStartRequested());
- // bring in a bunch of requests..
+ // Bring in a bunch of requests.
ConnectivityManager.NetworkCallback[] networkCallbacks =
new ConnectivityManager.NetworkCallback[10];
for (int i = 0; i< networkCallbacks.length; i++) {
networkCallbacks[i] = new ConnectivityManager.NetworkCallback();
NetworkRequest.Builder builder = new NetworkRequest.Builder();
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ builder.addCapability(capability);
mCm.requestNetwork(builder.build(), networkCallbacks[i]);
}
- testFactory.waitForNetworkRequests(11);
- assertEquals(false, testFactory.getMyStartRequested());
+ testFactory.waitForNetworkRequests(10 + expectedRequestCount);
+ assertFalse(testFactory.getMyStartRequested());
- // remove the requests
+ // Remove the requests.
for (int i = 0; i < networkCallbacks.length; i++) {
mCm.unregisterNetworkCallback(networkCallbacks[i]);
}
- testFactory.waitForNetworkRequests(1);
- assertEquals(false, testFactory.getMyStartRequested());
+ testFactory.waitForNetworkRequests(expectedRequestCount);
+ assertFalse(testFactory.getMyStartRequested());
- // drop the higher scored network
- cv = waitForConnectivityBroadcasts(1);
+ // Drop the higher scored network.
+ cv = testFactory.getNetworkStartedCV();
testAgent.disconnect();
waitFor(cv);
- assertEquals(1, testFactory.getMyRequestCount());
- assertEquals(true, testFactory.getMyStartRequested());
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertTrue(testFactory.getMyStartRequested());
testFactory.unregister();
+ if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
handlerThread.quit();
}
@LargeTest
+ public void testNetworkFactoryRequests() throws Exception {
+ tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
+ tryNetworkFactoryRequests(NET_CAPABILITY_DUN);
+ tryNetworkFactoryRequests(NET_CAPABILITY_FOTA);
+ tryNetworkFactoryRequests(NET_CAPABILITY_IMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_CBS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P);
+ tryNetworkFactoryRequests(NET_CAPABILITY_IA);
+ tryNetworkFactoryRequests(NET_CAPABILITY_RCS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_XCAP);
+ tryNetworkFactoryRequests(NET_CAPABILITY_EIMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED);
+ tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET);
+ tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED);
+ tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN);
+ // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
+ }
+
+ @LargeTest
public void testNoMutableNetworkRequests() throws Exception {
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ builder.addCapability(NET_CAPABILITY_VALIDATED);
try {
mCm.requestNetwork(builder.build(), new NetworkCallback());
fail();
@@ -807,7 +999,7 @@
fail();
} catch (IllegalArgumentException expected) {}
builder = new NetworkRequest.Builder();
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+ builder.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
try {
mCm.requestNetwork(builder.build(), new NetworkCallback());
fail();
@@ -818,6 +1010,71 @@
} 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();