Merge \"Make registerDefaultNetwork work on metered networks.\" into nyc-dev
am: 33cdc4d136 -s ours
Change-Id: Iacee53c01e7de163e677ab57bc2d7044f8aff952
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 5511a24..69f50a2 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -52,6 +52,15 @@
public boolean acceptUnvalidated;
/**
+ * Set to avoid surfacing the "Sign in to network" notification.
+ * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+ * procedure, a carrier specific provisioning notification will be placed.
+ * only one notification should be displayed. This field is set based on
+ * which notification should be used for provisioning.
+ */
+ public boolean provisioningNotificationDisabled;
+
+ /**
* For mobile networks, this is the subscriber ID (such as IMSI).
*/
public String subscriberId;
@@ -65,6 +74,7 @@
explicitlySelected = nm.explicitlySelected;
acceptUnvalidated = nm.acceptUnvalidated;
subscriberId = nm.subscriberId;
+ provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
}
}
@@ -79,6 +89,7 @@
out.writeInt(explicitlySelected ? 1 : 0);
out.writeInt(acceptUnvalidated ? 1 : 0);
out.writeString(subscriberId);
+ out.writeInt(provisioningNotificationDisabled ? 1 : 0);
}
public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -89,6 +100,7 @@
networkMisc.explicitlySelected = in.readInt() != 0;
networkMisc.acceptUnvalidated = in.readInt() != 0;
networkMisc.subscriberId = in.readString();
+ networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
return networkMisc;
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index f1edcbe..847d82b 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Defines a request for a network, made through {@link NetworkRequest.Builder} and used
* to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
@@ -47,15 +49,55 @@
public final int legacyType;
/**
+ * A NetworkRequest as used by the system can be one of three types:
+ *
+ * - LISTEN, for which the framework will issue callbacks about any
+ * and all networks that match the specified NetworkCapabilities,
+ *
+ * - REQUEST, capable of causing a specific network to be created
+ * first (e.g. a telephony DUN request), the framework will issue
+ * callbacks about the single, highest scoring current network
+ * (if any) that matches the specified NetworkCapabilities, or
+ *
+ * - TRACK_DEFAULT, a hybrid of the two designed such that the
+ * framework will issue callbacks for the single, highest scoring
+ * current network (if any) that matches the capabilities of the
+ * default Internet request (mDefaultRequest), but which cannot cause
+ * the framework to either create or retain the existence of any
+ * specific network.
+ *
+ * - The value NONE is used only by applications. When an application
+ * creates a NetworkRequest, it does not have a type; the type is set
+ * by the system depending on the method used to file the request
+ * (requestNetwork, registerNetworkCallback, etc.).
+ *
* @hide
*/
- public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) {
+ public static enum Type {
+ NONE,
+ LISTEN,
+ TRACK_DEFAULT,
+ REQUEST
+ };
+
+ /**
+ * The type of the request. This is only used by the system and is always NONE elsewhere.
+ *
+ * @hide
+ */
+ public final Type type;
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId, Type type) {
if (nc == null) {
throw new NullPointerException();
}
requestId = rId;
networkCapabilities = nc;
this.legacyType = legacyType;
+ this.type = type;
}
/**
@@ -65,6 +107,7 @@
networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
requestId = that.requestId;
this.legacyType = that.legacyType;
+ this.type = that.type;
}
/**
@@ -90,7 +133,7 @@
final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
nc.maybeMarkCapabilitiesRestricted();
return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
- ConnectivityManager.REQUEST_ID_UNSET);
+ ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
}
/**
@@ -223,6 +266,7 @@
dest.writeParcelable(networkCapabilities, flags);
dest.writeInt(legacyType);
dest.writeInt(requestId);
+ dest.writeString(type.name());
}
public static final Creator<NetworkRequest> CREATOR =
new Creator<NetworkRequest>() {
@@ -230,7 +274,8 @@
NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
int legacyType = in.readInt();
int requestId = in.readInt();
- NetworkRequest result = new NetworkRequest(nc, legacyType, requestId);
+ Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid.
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, type);
return result;
}
public NetworkRequest[] newArray(int size) {
@@ -238,8 +283,27 @@
}
};
+ /**
+ * Returns true iff. the contained NetworkRequest is one that:
+ *
+ * - should be associated with at most one satisfying network
+ * at a time;
+ *
+ * - should cause a network to be kept up if it is the best network
+ * which can satisfy the NetworkRequest.
+ *
+ * For full detail of how isRequest() is used for pairing Networks with
+ * NetworkRequests read rematchNetworkAndRequests().
+ *
+ * @hide
+ */
+ public boolean isRequest() {
+ return type == Type.TRACK_DEFAULT || type == Type.REQUEST;
+ }
+
public String toString() {
- return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType +
+ return "NetworkRequest [ " + type + " id=" + requestId +
+ (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
", " + networkCapabilities.toString() + " ]";
}
@@ -248,13 +312,11 @@
NetworkRequest that = (NetworkRequest)obj;
return (that.legacyType == this.legacyType &&
that.requestId == this.requestId &&
- ((that.networkCapabilities == null && this.networkCapabilities == null) ||
- (that.networkCapabilities != null &&
- that.networkCapabilities.equals(this.networkCapabilities))));
+ that.type == this.type &&
+ Objects.equals(that.networkCapabilities, this.networkCapabilities));
}
public int hashCode() {
- return requestId + (legacyType * 1013) +
- (networkCapabilities.hashCode() * 1051);
+ return Objects.hash(requestId, legacyType, networkCapabilities, type);
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 534b544..fb5b3f8 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -76,6 +76,7 @@
import android.net.UidRange;
import android.net.Uri;
import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.os.Binder;
import android.os.Build;
@@ -454,6 +455,8 @@
}
}
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -487,8 +490,16 @@
*
* The actual lists are populated when we scan the network types that
* are supported on this device.
+ *
+ * Threading model:
+ * - addSupportedType() is only called in the constructor
+ * - add(), update(), remove() are only called from the ConnectivityService handler thread.
+ * They are therefore not thread-safe with respect to each other.
+ * - getNetworkForType() can be called at any time on binder threads. It is synchronized
+ * on mTypeLists to be thread-safe with respect to a concurrent remove call.
+ * - dump is thread-safe with respect to concurrent add and remove calls.
*/
- private ArrayList<NetworkAgentInfo> mTypeLists[];
+ private final ArrayList<NetworkAgentInfo> mTypeLists[];
public LegacyTypeTracker() {
mTypeLists = (ArrayList<NetworkAgentInfo>[])
@@ -508,11 +519,12 @@
}
public NetworkAgentInfo getNetworkForType(int type) {
- if (isTypeSupported(type) && !mTypeLists[type].isEmpty()) {
- return mTypeLists[type].get(0);
- } else {
- return null;
+ synchronized (mTypeLists) {
+ if (isTypeSupported(type) && !mTypeLists[type].isEmpty()) {
+ return mTypeLists[type].get(0);
+ }
}
+ return null;
}
private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
@@ -535,12 +547,13 @@
if (list.contains(nai)) {
return;
}
-
- list.add(nai);
+ synchronized (mTypeLists) {
+ list.add(nai);
+ }
// 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) {
+ if ((list.size() == 1) || isDefaultNetwork) {
maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
}
@@ -552,11 +565,12 @@
if (list == null || list.isEmpty()) {
return;
}
-
final boolean wasFirstNetwork = list.get(0).equals(nai);
- if (!list.remove(nai)) {
- return;
+ synchronized (mTypeLists) {
+ if (!list.remove(nai)) {
+ return;
+ }
}
final DetailedState state = DetailedState.DISCONNECTED;
@@ -591,8 +605,8 @@
for (int type = 0; type < mTypeLists.length; type++) {
final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
final boolean contains = (list != null && list.contains(nai));
- final boolean isFirst = (list != null && list.size() > 0 && nai == list.get(0));
- if (isFirst || (contains && isDefault)) {
+ final boolean isFirst = contains && (nai == list.get(0));
+ if (isFirst || contains && isDefault) {
maybeLogBroadcast(nai, state, type, isDefault);
sendLegacyNetworkBroadcast(nai, state, type);
}
@@ -617,10 +631,12 @@
pw.println();
pw.println("Current state:");
pw.increaseIndent();
- for (int type = 0; type < mTypeLists.length; type++) {
- if (mTypeLists[type] == null|| mTypeLists[type].size() == 0) continue;
- for (NetworkAgentInfo nai : mTypeLists[type]) {
- pw.println(type + " " + naiToString(nai));
+ synchronized (mTypeLists) {
+ for (int type = 0; type < mTypeLists.length; type++) {
+ if (mTypeLists[type] == null || mTypeLists[type].isEmpty()) continue;
+ for (NetworkAgentInfo nai : mTypeLists[type]) {
+ pw.println(type + " " + naiToString(nai));
+ }
}
}
pw.decreaseIndent();
@@ -640,8 +656,7 @@
if (DBG) log("ConnectivityService starting up");
mDefaultRequest = createInternetRequestForTransport(-1);
- NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest,
- new Binder(), NetworkRequestType.REQUEST);
+ NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
mNetworkRequests.put(mDefaultRequest, defaultNRI);
mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
@@ -792,7 +807,19 @@
if (transportType > -1) {
netCap.addTransportType(transportType);
}
- return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
+ return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(),
+ NetworkRequest.Type.REQUEST);
+ }
+
+ // Used only for testing.
+ // TODO: Delete this and either:
+ // 1. Give Fake SettingsProvider the ability to send settings change notifications (requires
+ // changing ContentResolver to make registerContentObserver non-final).
+ // 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
+ // by subclassing SettingsObserver.
+ @VisibleForTesting
+ void updateMobileDataAlwaysOn() {
+ mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
}
private void handleMobileDataAlwaysOn() {
@@ -805,7 +832,7 @@
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
- null, mDefaultMobileDataRequest, new Binder(), NetworkRequestType.REQUEST));
+ null, mDefaultMobileDataRequest, new Binder()));
} else {
handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
}
@@ -1056,6 +1083,7 @@
return nai != null ? nai.network : null;
}
+ // Public because it's used by mLockdownTracker.
public NetworkInfo getActiveNetworkInfoUnfiltered() {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
@@ -1311,6 +1339,7 @@
* desired
* @return {@code true} on success, {@code false} on failure
*/
+ @Override
public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
enforceChangePermission();
if (mProtectedNetworks.contains(networkType)) {
@@ -1519,6 +1548,7 @@
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
+ // Public because it's used by mLockdownTracker.
public void sendConnectedBroadcast(NetworkInfo info) {
enforceConnectivityInternalPermission();
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
@@ -1877,8 +1907,8 @@
pw.increaseIndent();
pw.println("Requests:");
pw.increaseIndent();
- for (int i = 0; i < nai.networkRequests.size(); i++) {
- pw.println(nai.networkRequests.valueAt(i).toString());
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ pw.println(nai.requestAt(i).toString());
}
pw.decreaseIndent();
pw.println("Lingered:");
@@ -1987,10 +2017,6 @@
return false;
}
- private boolean isRequest(NetworkRequest request) {
- return mNetworkRequests.get(request).isRequest();
- }
-
// must be stateless - things change under us.
private class NetworkStateTrackerHandler extends Handler {
public NetworkStateTrackerHandler(Looper looper) {
@@ -2159,9 +2185,11 @@
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
- setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
- nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(),
- (PendingIntent)msg.obj, nai.networkMisc.explicitlySelected);
+ if (!nai.networkMisc.provisioningNotificationDisabled) {
+ setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
+ nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(),
+ (PendingIntent)msg.obj, nai.networkMisc.explicitlySelected);
+ }
}
break;
}
@@ -2179,7 +2207,7 @@
private void linger(NetworkAgentInfo nai) {
nai.lingering = true;
- NetworkEvent.logEvent(nai.network.netId, NetworkEvent.NETWORK_LINGER);
+ logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
}
@@ -2193,7 +2221,7 @@
nai.networkLingered.clear();
if (!nai.lingering) return;
nai.lingering = false;
- NetworkEvent.logEvent(nai.network.netId, NetworkEvent.NETWORK_UNLINGER);
+ logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
if (VDBG) log("Canceling linger of " + nai.name());
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
}
@@ -2205,7 +2233,7 @@
if (VDBG) log("NetworkFactory connected");
// A network factory has connected. Send it all current NetworkRequests.
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- if (!nri.isRequest()) continue;
+ if (!nri.request.isRequest()) continue;
NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
@@ -2240,7 +2268,7 @@
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai != null) {
if (DBG) {
- log(nai.name() + " got DISCONNECTED, was satisfying " + nai.networkRequests.size());
+ log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests());
}
// A network agent has disconnected.
// TODO - if we move the logic to the network agent (have them disconnect
@@ -2277,15 +2305,15 @@
mNetworkForNetId.remove(nai.network.netId);
}
// Remove all previously satisfied requests.
- for (int i = 0; i < nai.networkRequests.size(); i++) {
- NetworkRequest request = nai.networkRequests.valueAt(i);
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest request = nai.requestAt(i);
NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
mNetworkForRequestId.remove(request.requestId);
sendUpdatedScoreToFactories(request, 0);
}
}
- if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
+ if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
removeDataActivityTracking(nai);
notifyLockdownVpn(nai);
requestNetworkTransitionWakelock(nai.name());
@@ -2346,7 +2374,7 @@
private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
mNetworkRequests.put(nri.request, nri);
mNetworkRequestInfoLogs.log("REGISTER " + nri);
- if (!nri.isRequest()) {
+ if (!nri.request.isRequest()) {
for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
if (nri.request.networkCapabilities.hasSignalStrength() &&
network.satisfiesImmutableCapabilitiesOf(nri.request)) {
@@ -2355,7 +2383,7 @@
}
}
rematchAllNetworksAndRequests(null, 0);
- if (nri.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
+ if (nri.request.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
sendUpdatedScoreToFactories(nri.request, 0);
}
}
@@ -2376,8 +2404,8 @@
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 ||
+ if (nri.request.isRequest() && nai.satisfies(nri.request) &&
+ (nai.isSatisfyingRequest(nri.request.requestId) ||
// 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
@@ -2400,7 +2428,7 @@
if (DBG) log("Attempt to release unowned NetworkRequest " + request);
return;
}
- if (VDBG || (DBG && nri.isRequest())) log("releasing NetworkRequest " + request);
+ if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request);
nri.unlinkDeathRecipient();
mNetworkRequests.remove(request);
synchronized (mUidToNetworkRequestCount) {
@@ -2416,19 +2444,18 @@
}
}
mNetworkRequestInfoLogs.log("RELEASE " + nri);
- if (nri.isRequest()) {
+ if (nri.request.isRequest()) {
// Find all networks that are satisfying this request and remove the request
// from their request lists.
// TODO - it's my understanding that for a request there is only a single
// network satisfying it, so this loop is wasteful
boolean wasKept = false;
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- if (nai.networkRequests.get(nri.request.requestId) != null) {
- nai.networkRequests.remove(nri.request.requestId);
+ if (nai.isSatisfyingRequest(nri.request.requestId)) {
+ nai.removeRequest(nri.request.requestId);
if (VDBG) {
log(" Removing from current network " + nai.name() +
- ", leaving " + nai.networkRequests.size() +
- " requests.");
+ ", leaving " + nai.numNetworkRequests() + " requests.");
}
if (unneeded(nai)) {
if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
@@ -2455,10 +2482,10 @@
if (wasKept) {
// check if any of the remaining requests for this network are for the
// same legacy type - if so, don't remove the nai
- for (int i = 0; i < nai.networkRequests.size(); i++) {
- NetworkRequest otherRequest = nai.networkRequests.valueAt(i);
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest otherRequest = nai.requestAt(i);
if (otherRequest.legacyType == nri.request.legacyType &&
- isRequest(otherRequest)) {
+ otherRequest.isRequest()) {
if (DBG) log(" still have other legacy request - leaving");
doRemove = false;
}
@@ -2478,7 +2505,7 @@
// listens don't have a singular affectedNetwork. Check all networks to see
// if this listen request applies and remove it.
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- nai.networkRequests.remove(nri.request.requestId);
+ nai.removeRequest(nri.request.requestId);
if (nri.request.networkCapabilities.hasSignalStrength() &&
nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
@@ -2489,6 +2516,7 @@
}
}
+ @Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceConnectivityInternalPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
@@ -2562,6 +2590,7 @@
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+
setProvNotificationVisibleIntent(true, nai.network.netId, NotificationType.NO_INTERNET,
nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(), pendingIntent, true);
}
@@ -2671,6 +2700,7 @@
}
// javadoc from interface
+ @Override
public int tether(String iface) {
ConnectivityManager.enforceTetherChangePermission(mContext);
if (isTetheringSupported()) {
@@ -2688,6 +2718,7 @@
}
// javadoc from interface
+ @Override
public int untether(String iface) {
ConnectivityManager.enforceTetherChangePermission(mContext);
@@ -2706,6 +2737,7 @@
}
// javadoc from interface
+ @Override
public int getLastTetherError(String iface) {
enforceTetherAccessPermission();
@@ -2717,6 +2749,7 @@
}
// TODO - proper iface API for selection by property, inspection, etc
+ @Override
public String[] getTetherableUsbRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
@@ -2726,6 +2759,7 @@
}
}
+ @Override
public String[] getTetherableWifiRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
@@ -2735,6 +2769,7 @@
}
}
+ @Override
public String[] getTetherableBluetoothRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
@@ -2744,6 +2779,7 @@
}
}
+ @Override
public int setUsbTethering(boolean enable) {
ConnectivityManager.enforceTetherChangePermission(mContext);
if (isTetheringSupported()) {
@@ -2755,21 +2791,25 @@
// TODO - move iface listing, queries, etc to new module
// javadoc from interface
+ @Override
public String[] getTetherableIfaces() {
enforceTetherAccessPermission();
return mTethering.getTetherableIfaces();
}
+ @Override
public String[] getTetheredIfaces() {
enforceTetherAccessPermission();
return mTethering.getTetheredIfaces();
}
+ @Override
public String[] getTetheringErroredIfaces() {
enforceTetherAccessPermission();
return mTethering.getErroredIfaces();
}
+ @Override
public String[] getTetheredDhcpRanges() {
enforceConnectivityInternalPermission();
return mTethering.getTetheredDhcpRanges();
@@ -2828,12 +2868,14 @@
}
// 100 percent is full good, 0 is full bad.
+ @Override
public void reportInetCondition(int networkType, int percentage) {
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) return;
reportNetworkConnectivity(nai.network, percentage > 50);
}
+ @Override
public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
@@ -2878,6 +2920,7 @@
}
}
+ @Override
public ProxyInfo getProxyForNetwork(Network network) {
if (network == null) return getDefaultProxy();
final ProxyInfo globalProxy = getGlobalProxy();
@@ -3793,31 +3836,12 @@
}
}
- /**
- * A NetworkRequest as registered by an application can be one of three
- * types:
- *
- * - "listen", for which the framework will issue callbacks about any
- * and all networks that match the specified NetworkCapabilities,
- *
- * - "request", capable of causing a specific network to be created
- * first (e.g. a telephony DUN request), the framework will issue
- * callbacks about the single, highest scoring current network
- * (if any) that matches the specified NetworkCapabilities, or
- *
- * - "track the default network", a hybrid of the two designed such
- * that the framework will issue callbacks for the single, highest
- * scoring current network (if any) that matches the capabilities of
- * the default Internet request (mDefaultRequest), but which cannot
- * cause the framework to either create or retain the existence of
- * any specific network.
- *
- */
- private static enum NetworkRequestType {
- LISTEN,
- TRACK_DEFAULT,
- REQUEST
- };
+ private void ensureNetworkRequestHasType(NetworkRequest request) {
+ if (request.type == NetworkRequest.Type.NONE) {
+ throw new IllegalArgumentException(
+ "All NetworkRequests in ConnectivityService must have a type");
+ }
+ }
/**
* Tracks info about the requester.
@@ -3831,27 +3855,26 @@
final int mPid;
final int mUid;
final Messenger messenger;
- private final NetworkRequestType mType;
- NetworkRequestInfo(NetworkRequest r, PendingIntent pi, NetworkRequestType type) {
+ NetworkRequestInfo(NetworkRequest r, PendingIntent pi) {
request = r;
+ ensureNetworkRequestHasType(request);
mPendingIntent = pi;
messenger = null;
mBinder = null;
mPid = getCallingPid();
mUid = getCallingUid();
- mType = type;
enforceRequestCountLimit();
}
- NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, NetworkRequestType type) {
+ NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
super();
messenger = m;
request = r;
+ ensureNetworkRequestHasType(request);
mBinder = binder;
mPid = getCallingPid();
mUid = getCallingUid();
- mType = type;
mPendingIntent = null;
enforceRequestCountLimit();
@@ -3872,16 +3895,6 @@
}
}
- private String typeString() {
- switch (mType) {
- case LISTEN: return "Listen";
- case REQUEST: return "Request";
- case TRACK_DEFAULT: return "Track default";
- default:
- return "unknown type";
- }
- }
-
void unlinkDeathRecipient() {
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
@@ -3894,29 +3907,8 @@
releaseNetworkRequest(request);
}
- /**
- * Returns true iff. the contained NetworkRequest is one that:
- *
- * - should be associated with at most one satisfying network
- * at a time;
- *
- * - should cause a network to be kept up if it is the only network
- * which can satisfy the NetworkReqeust.
- *
- * For full detail of how isRequest() is used for pairing Networks with
- * NetworkRequests read rematchNetworkAndRequests().
- *
- * TODO: Rename to something more properly descriptive.
- */
- public boolean isRequest() {
- return (mType == NetworkRequestType.TRACK_DEFAULT) ||
- (mType == NetworkRequestType.REQUEST);
- }
-
public String toString() {
- return typeString() +
- " from uid/pid:" + mUid + "/" + mPid +
- " for " + request +
+ return "uid/pid:" + mUid + "/" + mPid + " " + request +
(mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
}
}
@@ -3966,18 +3958,21 @@
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
- final NetworkRequestType type = (networkCapabilities == null)
- ? NetworkRequestType.TRACK_DEFAULT
- : NetworkRequestType.REQUEST;
+ final NetworkRequest.Type type = (networkCapabilities == null)
+ ? NetworkRequest.Type.TRACK_DEFAULT
+ : NetworkRequest.Type.REQUEST;
// If the requested networkCapabilities is null, take them instead from
// the default network request. This allows callers to keep track of
// the system default network.
- if (type == NetworkRequestType.TRACK_DEFAULT) {
+ if (type == NetworkRequest.Type.TRACK_DEFAULT) {
networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
enforceAccessPermission();
} else {
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities);
+ // TODO: this is incorrect. We mark the request as metered or not depending on the state
+ // of the app when the request is filed, but we never change the request if the app
+ // changes network state. http://b/29964605
enforceMeteredApnPolicy(networkCapabilities);
}
ensureRequestableCapabilities(networkCapabilities);
@@ -3993,8 +3988,8 @@
}
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
- nextNetworkRequestId());
- NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, type);
+ nextNetworkRequestId(), type);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
if (DBG) log("requestNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
@@ -4064,9 +4059,8 @@
ensureRequestableCapabilities(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
- nextNetworkRequestId());
- NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
- NetworkRequestType.REQUEST);
+ nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
+ NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
if (DBG) log("pendingRequest for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
nri));
@@ -4116,9 +4110,9 @@
}
NetworkRequest networkRequest = new NetworkRequest(
- new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
- NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
- NetworkRequestType.LISTEN);
+ new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
+ NetworkRequest.Type.LISTEN);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
if (VDBG) log("listenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -4134,9 +4128,9 @@
}
NetworkRequest networkRequest = new NetworkRequest(
- new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
- NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
- NetworkRequestType.LISTEN);
+ new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
+ NetworkRequest.Type.LISTEN);
+ NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
if (VDBG) log("pendingListenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -4144,6 +4138,7 @@
@Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
+ ensureNetworkRequestHasType(networkRequest);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
0, networkRequest));
}
@@ -4452,10 +4447,10 @@
}
private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
- for (int i = 0; i < nai.networkRequests.size(); i++) {
- NetworkRequest nr = nai.networkRequests.valueAt(i);
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest nr = nai.requestAt(i);
// Don't send listening requests to factories. b/17393458
- if (!isRequest(nr)) continue;
+ if (!nr.isRequest()) continue;
sendUpdatedScoreToFactories(nr, nai.getCurrentScore());
}
}
@@ -4545,12 +4540,14 @@
}
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
- for (int i = 0; i < nai.networkRequests.size(); i++) {
- NetworkRequest nr = nai.networkRequests.valueAt(i);
- // Ignore listening requests.
- if (!isRequest(nr)) continue;
- loge("Dead network still had at least " + nr);
- break;
+ if (nai.numRequestNetworkRequests() != 0) {
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest nr = nai.requestAt(i);
+ // Ignore listening requests.
+ if (!nr.isRequest()) continue;
+ loge("Dead network still had at least " + nr);
+ break;
+ }
}
nai.asyncChannel.disconnect();
}
@@ -4632,7 +4629,7 @@
// check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
if (satisfies) {
- if (!nri.isRequest()) {
+ if (!nri.request.isRequest()) {
// This is not a request, it's a callback listener.
// Add it to newNetwork regardless of score.
if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
@@ -4651,7 +4648,7 @@
if (VDBG) log("rematch for " + newNetwork.name());
if (currentNetwork != null) {
if (VDBG) log(" accepting network in place of " + currentNetwork.name());
- currentNetwork.networkRequests.remove(nri.request.requestId);
+ currentNetwork.removeRequest(nri.request.requestId);
currentNetwork.networkLingered.add(nri.request);
affectedNetworks.add(currentNetwork);
} else {
@@ -4675,7 +4672,7 @@
oldDefaultNetwork = currentNetwork;
}
}
- } else if (newNetwork.networkRequests.get(nri.request.requestId) != null) {
+ } else if (newNetwork.isSatisfyingRequest(nri.request.requestId)) {
// If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",
// mark it as no longer satisfying "nri". Because networks are processed by
// rematchAllNetworkAndRequests() in descending score order, "currentNetwork" will
@@ -4687,12 +4684,12 @@
log("Network " + newNetwork.name() + " stopped satisfying" +
" request " + nri.request.requestId);
}
- newNetwork.networkRequests.remove(nri.request.requestId);
+ newNetwork.removeRequest(nri.request.requestId);
if (currentNetwork == newNetwork) {
mNetworkForRequestId.remove(nri.request.requestId);
sendUpdatedScoreToFactories(nri.request, 0);
} else {
- if (nri.isRequest()) {
+ if (nri.request.isRequest()) {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
newNetwork.name() +
" without updating mNetworkForRequestId or factories!");
@@ -4792,9 +4789,9 @@
// (notification callbacks) and then uses the old api (getNetworkInfo(type))
// they may get old info. Reverse this after the old startUsing api is removed.
// This is on top of the multiple intent sequencing referenced in the todo above.
- for (int i = 0; i < newNetwork.networkRequests.size(); i++) {
- NetworkRequest nr = newNetwork.networkRequests.valueAt(i);
- if (nr.legacyType != TYPE_NONE && isRequest(nr)) {
+ for (int i = 0; i < newNetwork.numNetworkRequests(); i++) {
+ NetworkRequest nr = newNetwork.requestAt(i);
+ if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
// legacy type tracker filters out repeat adds
mLegacyTypeTracker.add(nr.legacyType, newNetwork);
}
@@ -5053,7 +5050,7 @@
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
}
NetworkAgentInfo newDefaultAgent = null;
- if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
+ if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
newDefaultAgent = getDefaultNetwork();
if (newDefaultAgent != null) {
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
@@ -5073,8 +5070,8 @@
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
- for (int i = 0; i < networkAgent.networkRequests.size(); i++) {
- NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
+ for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
+ NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr);
if (nri.mPendingIntent == null) {
@@ -5215,7 +5212,7 @@
return new NetworkMonitor(context, handler, nai, defaultRequest);
}
- private static void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
+ private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
int newNetid = NETID_UNSET;
int prevNetid = NETID_UNSET;
int[] transports = new int[0];
@@ -5233,6 +5230,10 @@
hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
}
- DefaultNetworkEvent.logEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6);
+ mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+ }
+
+ private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
+ mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index d487bd0..15b872d 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -162,11 +162,13 @@
private static final int MAXIMUM_NETWORK_SCORE = 100;
// The list of NetworkRequests being satisfied by this Network.
- public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
+ private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
// The list of NetworkRequests that this Network previously satisfied with the highest
// score. A non-empty list indicates that if this Network was validated it is lingered.
// NOTE: This list is only used for debugging.
public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
+ // How many of the satisfied requests are actual requests and not listens.
+ private int mNumRequestNetworkRequests = 0;
public final Messenger messenger;
public final AsyncChannel asyncChannel;
@@ -188,18 +190,63 @@
networkMisc = misc;
}
+ // Functions for manipulating the requests satisfied by this network.
+ //
+ // These functions must only called on ConnectivityService's main thread.
+
/**
* Add {@code networkRequest} to this network as it's satisfied by this network.
- * NOTE: This function must only be called on ConnectivityService's main thread.
* @return true if {@code networkRequest} was added or false if {@code networkRequest} was
* already present.
*/
public boolean addRequest(NetworkRequest networkRequest) {
- if (networkRequests.get(networkRequest.requestId) == networkRequest) return false;
- networkRequests.put(networkRequest.requestId, networkRequest);
+ NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
+ if (existing == networkRequest) return false;
+ if (existing != null && existing.isRequest()) mNumRequestNetworkRequests--;
+ mNetworkRequests.put(networkRequest.requestId, networkRequest);
+ if (networkRequest.isRequest()) mNumRequestNetworkRequests++;
return true;
}
+ /**
+ * Remove the specified request from this network.
+ */
+ public void removeRequest(int requestId) {
+ NetworkRequest existing = mNetworkRequests.get(requestId);
+ if (existing != null && existing.isRequest()) mNumRequestNetworkRequests--;
+ mNetworkRequests.remove(requestId);
+ }
+
+ /**
+ * Returns whether this network is currently satisfying the request with the specified ID.
+ */
+ public boolean isSatisfyingRequest(int id) {
+ return mNetworkRequests.get(id) != null;
+ }
+
+ /**
+ * Returns the request at the specified position in the list of requests satisfied by this
+ * network.
+ */
+ public NetworkRequest requestAt(int index) {
+ return mNetworkRequests.valueAt(index);
+ }
+
+ /**
+ * Returns the number of requests currently satisfied by this network for which
+ * {@link android.net.NetworkRequest#isRequest} returns {@code true}.
+ */
+ public int numRequestNetworkRequests() {
+ return mNumRequestNetworkRequests;
+ }
+
+ /**
+ * Returns the number of requests of any type currently satisfied by this network.
+ */
+ public int numNetworkRequests() {
+ return mNetworkRequests.size();
+ }
+
// Does this network satisfy request?
public boolean satisfies(NetworkRequest request) {
return created &&
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 4fae4a7..d424717 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -26,6 +27,7 @@
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -61,12 +63,15 @@
import android.os.MessageQueue.IdleHandler;
import android.os.Process;
import android.os.SystemClock;
+import android.provider.Settings;
import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.util.LogPrinter;
+import com.android.internal.util.FakeSettingsProvider;
import com.android.internal.util.WakeupMessage;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
@@ -75,6 +80,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -96,6 +102,7 @@
private WrappedConnectivityManager mCm;
private MockNetworkAgent mWiFiNetworkAgent;
private MockNetworkAgent mCellNetworkAgent;
+ private MockNetworkAgent mEthernetNetworkAgent;
// This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
// do not go through ConnectivityService but talk to netd directly, so they don't automatically
@@ -118,27 +125,24 @@
}
private class MockContext extends BroadcastInterceptingContext {
+ private final MockContentResolver mContentResolver;
+
MockContext(Context base) {
super(base);
+ mContentResolver = new MockContentResolver();
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
}
@Override
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- // PendingIntents sent by the AlarmManager are not intercepted by
- // BroadcastInterceptingContext so we must really register the receiver.
- // This shouldn't effect the real NetworkMonitors as the action contains a random token.
- if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) {
- return getBaseContext().registerReceiver(receiver, filter);
- } else {
- return super.registerReceiver(receiver, filter);
- }
- }
-
- @Override
- public Object getSystemService (String name) {
+ public Object getSystemService(String name) {
if (name == Context.CONNECTIVITY_SERVICE) return mCm;
return super.getSystemService(name);
}
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
}
/**
@@ -242,6 +246,9 @@
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(transport);
switch (transport) {
+ case TRANSPORT_ETHERNET:
+ mScore = 70;
+ break;
case TRANSPORT_WIFI:
mScore = 60;
break;
@@ -303,6 +310,11 @@
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
+ public void removeCapability(int capability) {
+ mNetworkCapabilities.removeCapability(capability);
+ mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ }
+
public void setSignalStrength(int signalStrength) {
mNetworkCapabilities.setSignalStrength(signalStrength);
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -641,7 +653,6 @@
public void waitForIdle() {
waitForIdle(TIMEOUT_MS);
}
-
}
private interface Criteria {
@@ -694,14 +705,23 @@
mCm.bindProcessToNetwork(null);
}
+ public void tearDown() throws Exception {
+ if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
+ if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
+ mCellNetworkAgent = mWiFiNetworkAgent = null;
+ super.tearDown();
+ }
+
private int transportToLegacyType(int transport) {
switch (transport) {
+ case TRANSPORT_ETHERNET:
+ return TYPE_ETHERNET;
case TRANSPORT_WIFI:
return TYPE_WIFI;
case TRANSPORT_CELLULAR:
return TYPE_MOBILE;
default:
- throw new IllegalStateException("Unknown transport" + transport);
+ throw new IllegalStateException("Unknown transport " + transport);
}
}
@@ -911,8 +931,6 @@
mWiFiNetworkAgent.adjustScore(11);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
- mCellNetworkAgent.disconnect();
- mWiFiNetworkAgent.disconnect();
}
@LargeTest
@@ -984,8 +1002,6 @@
assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
NET_CAPABILITY_VALIDATED));
verifyActiveNetwork(TRANSPORT_WIFI);
- mCellNetworkAgent.disconnect();
- mWiFiNetworkAgent.disconnect();
}
@LargeTest
@@ -1012,8 +1028,6 @@
assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
NET_CAPABILITY_VALIDATED));
verifyActiveNetwork(TRANSPORT_WIFI);
- mCellNetworkAgent.disconnect();
- mWiFiNetworkAgent.disconnect();
}
enum CallbackState {
@@ -1029,59 +1043,79 @@
* received. assertNoCallback may be called at any time.
*/
private class TestNetworkCallback extends NetworkCallback {
- private final ConditionVariable mConditionVariable = new ConditionVariable();
- private CallbackState mLastCallback = CallbackState.NONE;
- private Network mLastNetwork;
+ // Chosen to be much less than the linger timeout. This ensures that we can distinguish
+ // between a LOST callback that arrives immediately and a LOST callback that arrives after
+ // the linger timeout.
+ private final static int TIMEOUT_MS = 50;
+
+ private class CallbackInfo {
+ public final CallbackState state;
+ public final Network network;
+ public CallbackInfo(CallbackState s, Network n) { state = s; network = n; }
+ public String toString() { return String.format("%s (%s)", state, network); }
+ public boolean equals(Object o) {
+ if (!(o instanceof CallbackInfo)) return false;
+ CallbackInfo other = (CallbackInfo) o;
+ return state == other.state && Objects.equals(network, other.network);
+ }
+ }
+ private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
+
+ private void setLastCallback(CallbackState state, Network network) {
+ mCallbacks.offer(new CallbackInfo(state, network));
+ }
public void onAvailable(Network network) {
- assertEquals(CallbackState.NONE, mLastCallback);
- mLastCallback = CallbackState.AVAILABLE;
- mLastNetwork = network;
- mConditionVariable.open();
+ setLastCallback(CallbackState.AVAILABLE, network);
}
public void onLosing(Network network, int maxMsToLive) {
- assertEquals(CallbackState.NONE, mLastCallback);
- mLastCallback = CallbackState.LOSING;
- mLastNetwork = network;
- mConditionVariable.open();
+ setLastCallback(CallbackState.LOSING, network);
}
public void onLost(Network network) {
- assertEquals(CallbackState.NONE, mLastCallback);
- mLastCallback = CallbackState.LOST;
- mLastNetwork = network;
- mConditionVariable.open();
- }
-
- void expectCallback(CallbackState state) {
- expectCallback(state, null);
+ setLastCallback(CallbackState.LOST, network);
}
void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
- waitFor(mConditionVariable);
- assertEquals(state, mLastCallback);
- if (mockAgent != null) {
- assertEquals(mockAgent.getNetwork(), mLastNetwork);
+ CallbackInfo expected = new CallbackInfo(
+ state,
+ (mockAgent != null) ? mockAgent.getNetwork() : null);
+ try {
+ assertEquals("Unexpected callback:",
+ expected, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
}
- mLastCallback = CallbackState.NONE;
- mLastNetwork = null;
- mConditionVariable.close();
}
void assertNoCallback() {
- assertEquals(CallbackState.NONE, mLastCallback);
+ mService.waitForIdle();
+ CallbackInfo c = mCallbacks.peek();
+ assertNull("Unexpected callback: " + c, c);
+ }
+ }
+
+ // Can't be part of TestNetworkCallback because "cannot be declared static; static methods can
+ // only be declared in a static or top level type".
+ static void assertNoCallbacks(TestNetworkCallback ... callbacks) {
+ for (TestNetworkCallback c : callbacks) {
+ c.assertNoCallback();
}
}
@LargeTest
public void testStateChangeNetworkCallbacks() throws Exception {
+ final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest genericRequest = new NetworkRequest.Builder()
+ .clearCapabilities().build();
final NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI).build();
final NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
@@ -1089,65 +1123,241 @@
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE);
- wifiNetworkCallback.assertNoCallback();
+ genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
mService.waitForIdle();
- wifiNetworkCallback.assertNoCallback();
- cellNetworkCallback.assertNoCallback();
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
cv = waitForConnectivityBroadcasts(2);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE);
- cellNetworkCallback.assertNoCallback();
+ genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
cv = waitForConnectivityBroadcasts(2);
mWiFiNetworkAgent.disconnect();
- wifiNetworkCallback.expectCallback(CallbackState.LOST);
+ genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
cellNetworkCallback.assertNoCallback();
waitFor(cv);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent.disconnect();
- cellNetworkCallback.expectCallback(CallbackState.LOST);
- wifiNetworkCallback.assertNoCallback();
+ genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
waitFor(cv);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
// Test validated networks
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE);
- wifiNetworkCallback.assertNoCallback();
+ genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
mService.waitForIdle();
- wifiNetworkCallback.assertNoCallback();
- cellNetworkCallback.assertNoCallback();
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE);
- cellNetworkCallback.expectCallback(CallbackState.LOSING);
+ genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
+
+ mWiFiNetworkAgent.disconnect();
+ genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
+
+ mCellNetworkAgent.disconnect();
+ genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
+ }
+
+ @SmallTest
+ public void testMultipleLingering() {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+
+ mCellNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ mWiFiNetworkAgent.connect(true);
+ // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
+ // We then get LOSING when wifi validates and cell is outscored.
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ mEthernetNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+ assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ mEthernetNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+
+ for (int i = 0; i < 4; i++) {
+ MockNetworkAgent oldNetwork, newNetwork;
+ if (i % 2 == 0) {
+ mWiFiNetworkAgent.adjustScore(-15);
+ oldNetwork = mWiFiNetworkAgent;
+ newNetwork = mCellNetworkAgent;
+ } else {
+ mWiFiNetworkAgent.adjustScore(15);
+ oldNetwork = mCellNetworkAgent;
+ newNetwork = mWiFiNetworkAgent;
+
+ }
+ callback.expectCallback(CallbackState.LOSING, oldNetwork);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork);
+ assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
+ }
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even
+ // if the network is still up.
+ mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // Wifi no longer satisfies our listen, which is for an unmetered network.
+ // But because its score is 55, it's still up (and the default network).
+ defaultCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Disconnect our test networks.
+ mWiFiNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ mCellNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+
+ mCm.unregisterNetworkCallback(callback);
+ mService.waitForIdle();
+
+ // Check that a network is only lingered or torn down if it would not satisfy a request even
+ // if it validated.
+ request = new NetworkRequest.Builder().clearCapabilities().build();
+ callback = new TestNetworkCallback();
+
+ mCm.registerNetworkCallback(request, callback);
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(false); // Score: 10
+ callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Bring up wifi with a score of 20.
+ // Cell stays up because it would satisfy the default request if it validated.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false); // Score: 20
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
- wifiNetworkCallback.expectCallback(CallbackState.LOST);
- cellNetworkCallback.assertNoCallback();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
- mCellNetworkAgent.disconnect();
- cellNetworkCallback.expectCallback(CallbackState.LOST);
- wifiNetworkCallback.assertNoCallback();
+ // Bring up wifi with a score of 70.
+ // Cell is lingered because it would not satisfy any request, even if it validated.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.adjustScore(50);
+ mWiFiNetworkAgent.connect(false); // Score: 70
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Tear down wifi.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Bring up wifi, then validate it. In this case we do not linger cell. What happens is that
+ // when wifi connects, we don't linger because cell could potentially become the default
+ // network if it validated. Then, when wifi validates, we re-evaluate cell, see it has no
+ // requests, and tear it down because it's unneeded.
+ // TODO: can we linger in this case?
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // The current code has a bug: if a network is lingering, and we add and then remove a
+ // request from it, we forget that the network was lingering and tear it down immediately.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+
+ NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ NetworkCallback noopCallback = new NetworkCallback();
+ mCm.requestNetwork(cellRequest, noopCallback);
+ mCm.unregisterNetworkCallback(noopCallback);
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ mCm.unregisterNetworkCallback(callback);
+ mCm.unregisterNetworkCallback(defaultCallback);
}
private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -1314,7 +1524,7 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mCellNetworkAgent.connectWithoutInternet();
- networkCallback.expectCallback(CallbackState.AVAILABLE);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
verifyActiveNetwork(TRANSPORT_WIFI);
// Test releasing NetworkRequest disconnects cellular with MMS
cv = mCellNetworkAgent.getDisconnectedCV();
@@ -1340,7 +1550,7 @@
MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mmsNetworkAgent.connectWithoutInternet();
- networkCallback.expectCallback(CallbackState.AVAILABLE);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mmsNetworkAgent);
verifyActiveNetwork(TRANSPORT_CELLULAR);
// Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
cv = mmsNetworkAgent.getDisconnectedCV();
@@ -1366,36 +1576,36 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
- captivePortalCallback.expectCallback(CallbackState.AVAILABLE);
+ captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
// Take down network.
// Expect onLost callback.
mWiFiNetworkAgent.disconnect();
- captivePortalCallback.expectCallback(CallbackState.LOST);
+ captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Bring up a network with a captive portal.
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
- captivePortalCallback.expectCallback(CallbackState.AVAILABLE);
+ captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
- captivePortalCallback.expectCallback(CallbackState.LOST);
+ captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectCallback(CallbackState.AVAILABLE);
+ validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
- validatedCallback.expectCallback(CallbackState.LOST);
+ validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
}
@SmallTest
@@ -1410,7 +1620,7 @@
// do nothing - should get here
}
- assertTrue("NetworkReqeuest builder with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
+ assertTrue("NetworkRequest builder with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
execptionCalled);
try {
@@ -1477,6 +1687,145 @@
defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
}
+ @SmallTest
+ public void testRequestBenchmark() throws Exception {
+ // Benchmarks connecting and switching performance in the presence of a large number of
+ // NetworkRequests.
+ // 1. File NUM_REQUESTS requests.
+ // 2. Have a network connect. Wait for NUM_REQUESTS onAvailable callbacks to fire.
+ // 3. Have a new network connect and outscore the previous. Wait for NUM_REQUESTS onLosing
+ // and NUM_REQUESTS onAvailable callbacks to fire.
+ // See how long it took.
+ final int NUM_REQUESTS = 90;
+ final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS];
+ final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
+ final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS);
+
+ final int REGISTER_TIME_LIMIT_MS = 100;
+ long startTime = System.currentTimeMillis();
+ for (int i = 0; i < NUM_REQUESTS; i++) {
+ callbacks[i] = new NetworkCallback() {
+ @Override public void onAvailable(Network n) { availableLatch.countDown(); }
+ @Override public void onLosing(Network n, int t) { losingLatch.countDown(); }
+ };
+ mCm.registerNetworkCallback(request, callbacks[i]);
+ }
+ long timeTaken = System.currentTimeMillis() - startTime;
+ String msg = String.format("Register %d callbacks: %dms, acceptable %dms",
+ NUM_REQUESTS, timeTaken, REGISTER_TIME_LIMIT_MS);
+ Log.d(TAG, msg);
+ assertTrue(msg, timeTaken < REGISTER_TIME_LIMIT_MS);
+
+ final int CONNECT_TIME_LIMIT_MS = 30;
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ // Don't request that the network validate, because otherwise connect() will block until
+ // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
+ // and we won't actually measure anything.
+ mCellNetworkAgent.connect(false);
+ startTime = System.currentTimeMillis();
+ if (!availableLatch.await(CONNECT_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
+ fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
+ NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+ CONNECT_TIME_LIMIT_MS));
+ }
+ timeTaken = System.currentTimeMillis() - startTime;
+ Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
+ NUM_REQUESTS, timeTaken, CONNECT_TIME_LIMIT_MS));
+
+ final int SWITCH_TIME_LIMIT_MS = 30;
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ // Give wifi a high enough score that we'll linger cell when wifi comes up.
+ mWiFiNetworkAgent.adjustScore(40);
+ mWiFiNetworkAgent.connect(false);
+ startTime = System.currentTimeMillis();
+ if (!losingLatch.await(SWITCH_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
+ fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
+ NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
+ }
+ timeTaken = System.currentTimeMillis() - startTime;
+ Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
+ NUM_REQUESTS, timeTaken, SWITCH_TIME_LIMIT_MS));
+
+ final int UNREGISTER_TIME_LIMIT_MS = 10;
+ startTime = System.currentTimeMillis();
+ for (int i = 0; i < NUM_REQUESTS; i++) {
+ mCm.unregisterNetworkCallback(callbacks[i]);
+ }
+ timeTaken = System.currentTimeMillis() - startTime;
+ msg = String.format("Unregister %d callbacks: %dms, acceptable %dms",
+ NUM_REQUESTS, timeTaken, UNREGISTER_TIME_LIMIT_MS);
+ Log.d(TAG, msg);
+ assertTrue(msg, timeTaken < UNREGISTER_TIME_LIMIT_MS);
+ }
+
+ @SmallTest
+ public void testMobileDataAlwaysOn() throws Exception {
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+
+ final HandlerThread handlerThread = new HandlerThread("MobileDataAlwaysOnFactory");
+ handlerThread.start();
+ NetworkCapabilities filter = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter);
+ testFactory.setScoreFilter(40);
+
+ // Register the factory and expect it to start looking for a network.
+ testFactory.expectAddRequests(1);
+ testFactory.register();
+ testFactory.waitForNetworkRequests(1);
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Bring up wifi. The factory stops looking for a network.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ testFactory.expectAddRequests(2); // Because the default request changes score twice.
+ mWiFiNetworkAgent.connect(true);
+ testFactory.waitForNetworkRequests(1);
+ assertFalse(testFactory.getMyStartRequested());
+
+ ContentResolver cr = mServiceContext.getContentResolver();
+
+ // Turn on mobile data always on. The factory starts looking again.
+ testFactory.expectAddRequests(1);
+ Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 1);
+ mService.updateMobileDataAlwaysOn();
+ testFactory.waitForNetworkRequests(2);
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Bring up cell data and check that the factory stops looking.
+ assertEquals(1, mCm.getAllNetworks().length);
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ testFactory.expectAddRequests(2); // Because the cell request changes score twice.
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ testFactory.waitForNetworkRequests(2);
+ assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
+
+ // Check that cell data stays up.
+ mService.waitForIdle();
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ assertEquals(2, mCm.getAllNetworks().length);
+
+ // Turn off mobile data always on and expect the request to disappear...
+ testFactory.expectRemoveRequests(1);
+ Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 0);
+ mService.updateMobileDataAlwaysOn();
+ testFactory.waitForNetworkRequests(1);
+
+ // ... and cell data to be torn down.
+ cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ assertEquals(1, mCm.getAllNetworks().length);
+
+ testFactory.unregister();
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ handlerThread.quit();
+ }
+
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java
new file mode 100644
index 0000000..033b2c9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.metrics.DnsEvent;
+import android.net.metrics.IDnsEventListener;
+import android.net.metrics.IpConnectivityLog;
+
+import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+public class DnsEventListenerServiceTest extends TestCase {
+
+ // TODO: read from DnsEventListenerService after this constant is read from system property
+ static final int BATCH_SIZE = 100;
+ static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO;
+ // TODO: read from IDnsEventListener
+ static final int RETURN_CODE = 1;
+
+ static final byte[] EVENT_TYPES = new byte[BATCH_SIZE];
+ static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
+ static final int[] LATENCIES = new int[BATCH_SIZE];
+ static {
+ for (int i = 0; i < BATCH_SIZE; i++) {
+ EVENT_TYPES[i] = EVENT_TYPE;
+ RETURN_CODES[i] = RETURN_CODE;
+ LATENCIES[i] = i;
+ }
+ }
+
+ DnsEventListenerService mDnsService;
+
+ @Mock ConnectivityManager mCm;
+ @Mock IpConnectivityLog mLog;
+ ArgumentCaptor<NetworkCallback> mCallbackCaptor;
+ ArgumentCaptor<DnsEvent> mEvCaptor;
+
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
+ mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
+ mDnsService = new DnsEventListenerService(mCm, mLog);
+
+ verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
+ }
+
+ public void testOneBatch() throws Exception {
+ log(105, LATENCIES);
+ log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
+
+ verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
+
+ log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
+
+ mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
+ verifyLoggedEvents(
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ }
+
+ public void testSeveralBatches() throws Exception {
+ log(105, LATENCIES);
+ log(106, LATENCIES);
+ log(105, LATENCIES);
+ log(107, LATENCIES);
+
+ verifyLoggedEvents(
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ }
+
+ public void testBatchAndNetworkLost() throws Exception {
+ byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
+ byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
+ int[] latencies = Arrays.copyOf(LATENCIES, 20);
+
+ log(105, LATENCIES);
+ log(105, latencies);
+ mCallbackCaptor.getValue().onLost(new Network(105));
+ log(105, LATENCIES);
+
+ verifyLoggedEvents(
+ new DnsEvent(105, eventTypes, returnCodes, latencies),
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ }
+
+ public void testConcurrentBatchesAndDumps() throws Exception {
+ final long stop = System.currentTimeMillis() + 100;
+ final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
+ new Thread() {
+ public void run() {
+ while (System.currentTimeMillis() < stop) {
+ mDnsService.dump(pw);
+ }
+ }
+ }.start();
+
+ logAsync(105, LATENCIES);
+ logAsync(106, LATENCIES);
+ logAsync(107, LATENCIES);
+
+ verifyLoggedEvents(500,
+ new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
+ new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ }
+
+ public void testConcurrentBatchesAndNetworkLoss() throws Exception {
+ logAsync(105, LATENCIES);
+ Thread.sleep(10L);
+ // call onLost() asynchronously to logAsync's onDnsEvent() calls.
+ mCallbackCaptor.getValue().onLost(new Network(105));
+
+ // do not verify unpredictable batch
+ verify(mLog, timeout(500).times(1)).log(any());
+ }
+
+ void log(int netId, int[] latencies) {
+ for (int l : latencies) {
+ mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l);
+ }
+ }
+
+ void logAsync(int netId, int[] latencies) {
+ new Thread() {
+ public void run() {
+ log(netId, latencies);
+ }
+ }.start();
+ }
+
+ void verifyLoggedEvents(DnsEvent... expected) {
+ verifyLoggedEvents(0, expected);
+ }
+
+ void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) {
+ verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture());
+ for (DnsEvent got : mEvCaptor.getAllValues()) {
+ OptionalInt index = IntStream.range(0, expectedEvents.length)
+ .filter(i -> eventsEqual(expectedEvents[i], got))
+ .findFirst();
+ // Don't match same expected event more than once.
+ index.ifPresent(i -> expectedEvents[i] = null);
+ assertTrue(index.isPresent());
+ }
+ }
+
+ /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
+ static boolean eventsEqual(DnsEvent expected, DnsEvent got) {
+ return (expected == got) || ((expected != null) && (got != null)
+ && (expected.netId == got.netId)
+ && Arrays.equals(expected.eventTypes, got.eventTypes)
+ && Arrays.equals(expected.returnCodes, got.returnCodes)
+ && Arrays.equals(expected.latenciesMs, got.latenciesMs));
+ }
+}