Merge "Fix NOT_RESTRICTED network capability and enforce it." into mnc-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 4d45076..605acb0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,6 +22,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
@@ -33,6 +34,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
@@ -490,6 +492,8 @@
*/
private static ConnectivityManager sInstance;
+ private final Context mContext;
+
private INetworkManagementService mNMService;
/**
@@ -892,9 +896,11 @@
*
* @deprecated Deprecated in favor of the cleaner
* {@link #requestNetwork(NetworkRequest, NetworkCallback)} API.
- * @removed
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
if (netCap == null) {
Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
@@ -939,10 +945,12 @@
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*
- * @deprecated Deprecated in favor of the cleaner {@link unregisterNetworkCallback} API.
- * @removed
+ * @deprecated Deprecated in favor of the cleaner {@link #unregisterNetworkCallback} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
if (netCap == null) {
Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
@@ -1185,7 +1193,8 @@
* @deprecated Deprecated in favor of the
* {@link #requestNetwork(NetworkRequest, NetworkCallback)},
* {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API.
- * @removed
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
@@ -1204,9 +1213,9 @@
* @hide
* @deprecated Deprecated in favor of the {@link #requestNetwork} and
* {@link #bindProcessToNetwork} API.
- * @removed
*/
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
+ checkLegacyRoutingApiAccess();
try {
return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress());
} catch (RemoteException e) {
@@ -1385,7 +1394,8 @@
/**
* {@hide}
*/
- public ConnectivityManager(IConnectivityManager service) {
+ public ConnectivityManager(Context context, IConnectivityManager service) {
+ mContext = checkNotNull(context, "missing context");
mService = checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}
@@ -2672,6 +2682,34 @@
return new Network(netId);
}
+ private void unsupportedStartingFrom(int version) {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // The getApplicationInfo() call we make below is not supported in system context, and
+ // we want to allow the system to use these APIs anyway.
+ return;
+ }
+
+ if (mContext.getApplicationInfo().targetSdkVersion >= version) {
+ throw new UnsupportedOperationException(
+ "This method is not supported in target SDK version " + version + " and above");
+ }
+ }
+
+ // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature,
+ // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException.
+ // TODO: convert the existing system users (Tethering, GpsLocationProvider) to the new APIs and
+ // remove these exemptions. Note that this check is not secure, and apps can still access these
+ // functions by accessing ConnectivityService directly. However, it should be clear that doing
+ // so is unsupported and may break in the future. http://b/22728205
+ private void checkLegacyRoutingApiAccess() {
+ if (mContext.checkCallingOrSelfPermission("com.android.permission.INJECT_OMADM_SETTINGS")
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ unsupportedStartingFrom(VERSION_CODES.M);
+ }
+
/**
* Binds host resolutions performed by this process to {@code network}.
* {@link #bindProcessToNetwork} takes precedence over this setting.
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index cfd5bf1..c4de4a2 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -662,6 +662,17 @@
}
/**
+ * Returns true if this link or any of its stacked interfaces has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ */
+ private boolean hasIPv4AddressOnInterface(String iface) {
+ return (mIfaceName.equals(iface) && hasIPv4Address()) ||
+ (iface != null && mStackedLinks.containsKey(iface) &&
+ mStackedLinks.get(iface).hasIPv4Address());
+ }
+
+ /**
* Returns true if this link has a global preferred IPv6 address.
*
* @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
@@ -792,7 +803,7 @@
if (ip instanceof Inet4Address) {
// For IPv4, it suffices for now to simply have any address.
- return hasIPv4Address();
+ return hasIPv4AddressOnInterface(bestRoute.getInterface());
} else if (ip instanceof Inet6Address) {
if (ip.isLinkLocalAddress()) {
// For now, just make sure link-local destinations have
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index e6fc1ea..808a882 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -148,6 +148,13 @@
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
+ /**
+ * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
+ * automatically reconnecting to this network (e.g. via autojoin). Happens
+ * when user selects "No" option on the "Stay connected?" dialog box.
+ */
+ public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 11;
+
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null);
@@ -240,6 +247,11 @@
}
case CMD_SAVE_ACCEPT_UNVALIDATED: {
saveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ preventAutomaticReconnect();
+ break;
}
}
}
@@ -365,6 +377,15 @@
protected void saveAcceptUnvalidated(boolean accept) {
}
+ /**
+ * Called when the user asks to not stay connected to this network because it was found to not
+ * provide Internet access. Usually followed by call to {@code unwanted}. The transport is
+ * responsible for making sure the device does not automatically reconnect to the same network
+ * after the {@code unwanted} call.
+ */
+ protected void preventAutomaticReconnect() {
+ }
+
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 0154eb5..29b063a 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -661,6 +661,7 @@
case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break;
case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break;
case NET_CAPABILITY_VALIDATED: capabilities += "VALIDATED"; break;
+ case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
}
if (++i < types.length) capabilities += "&";
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 62e8532..0802d30 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1772,7 +1772,7 @@
// Start gathering diagnostic information.
netDiags.add(new NetworkDiagnostics(
nai.network,
- new LinkProperties(nai.linkProperties),
+ new LinkProperties(nai.linkProperties), // Must be a copy.
DIAG_TIME_MS));
}
@@ -2142,6 +2142,10 @@
mDefaultInetConditionPublished = 0;
}
notifyIfacesChanged();
+ // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
+ // by other networks that are already connected. Perhaps that can be done by
+ // sending all CALLBACK_LOST messages (for requests, not listens) at the end
+ // of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
mNetworkAgentInfos.remove(msg.replyTo);
@@ -2250,7 +2254,7 @@
// 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
+ // is currently satisfying the request. This is desirable when
// WiFi ends up validating and out scoring cellular.
mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
nai.getCurrentScoreAsValidated())) {
@@ -2378,14 +2382,10 @@
}
if (!accept) {
- // Tell the NetworkAgent that the network does not have Internet access (because that's
- // what we just told the user). This will hint to Wi-Fi not to autojoin this network in
- // the future. We do this now because NetworkMonitor might not yet have finished
- // validating and thus we might not yet have received an EVENT_NETWORK_TESTED.
- nai.asyncChannel.sendMessage(NetworkAgent.CMD_REPORT_NETWORK_STATUS,
- NetworkAgent.INVALID_NETWORK, 0, null);
- // TODO: Tear the network down once we have determined how to tell WifiStateMachine not
- // to reconnect to it immediately. http://b/20739299
+ // Tell the NetworkAgent to not automatically reconnect to the network.
+ nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+ // Teardown the nework.
+ teardownUnneededNetwork(nai);
}
}
@@ -3048,7 +3048,10 @@
@Override
public LegacyVpnInfo getLegacyVpnInfo(int userId) {
enforceCrossUserPermission(userId);
- throwIfLockdownEnabled();
+ if (mLockdownEnabled) {
+ return null;
+ }
+
synchronized(mVpns) {
return mVpns.get(userId).getLegacyVpnInfo();
}
@@ -3799,10 +3802,10 @@
// TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
// satisfies mDefaultRequest.
- NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
+ final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
- mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest);
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
}
@@ -4206,8 +4209,9 @@
ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();
if (VDBG) log(" network has: " + newNetwork.networkCapabilities);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
- if (newNetwork == currentNetwork) {
+ final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ final boolean satisfies = newNetwork.satisfies(nri.request);
+ if (newNetwork == currentNetwork && satisfies) {
if (VDBG) {
log("Network " + newNetwork.name() + " was already satisfying" +
" request " + nri.request.requestId + ". No change.");
@@ -4218,7 +4222,7 @@
// check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
- if (newNetwork.satisfies(nri.request)) {
+ if (satisfies) {
if (!nri.isRequest) {
// This is not a request, it's a callback listener.
// Add it to newNetwork regardless of score.
@@ -4261,6 +4265,37 @@
oldDefaultNetwork = currentNetwork;
}
}
+ } else if (newNetwork.networkRequests.get(nri.request.requestId) != null) {
+ // 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
+ // match "newNetwork" before this loop will encounter a "currentNetwork" with higher
+ // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".
+ // This means this code doesn't have to handle the case where "currentNetwork" no
+ // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork".
+ if (DBG) {
+ log("Network " + newNetwork.name() + " stopped satisfying" +
+ " request " + nri.request.requestId);
+ }
+ newNetwork.networkRequests.remove(nri.request.requestId);
+ if (currentNetwork == newNetwork) {
+ mNetworkForRequestId.remove(nri.request.requestId);
+ sendUpdatedScoreToFactories(nri.request, 0);
+ } else {
+ if (nri.isRequest == true) {
+ Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
+ newNetwork.name() +
+ " without updating mNetworkForRequestId or factories!");
+ }
+ }
+ // TODO: technically, sending CALLBACK_LOST here is
+ // incorrect if nri is a request (not a listen) and there
+ // is a replacement network currently connected that can
+ // satisfy it. However, the only capability that can both
+ // a) be requested and b) change is NET_CAPABILITY_TRUSTED,
+ // so this code is only incorrect for a network that loses
+ // the TRUSTED capability, which is a rare case.
+ callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST);
}
}
// Linger any networks that are no longer needed.
@@ -4279,41 +4314,41 @@
unlinger(nai);
}
}
+ if (isNewDefault) {
+ // Notify system services that this network is up.
+ makeDefault(newNetwork);
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in
+ // a second if it's held. The second pause is to allow apps
+ // to reconnect over the new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
+ }
+ }
+ }
+
+ // do this after the default net is switched, but
+ // before LegacyTypeTracker sends legacy broadcasts
+ for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
+
+ if (isNewDefault) {
+ // Maintain the illusion: since the legacy API only
+ // understands one network at a time, we must pretend
+ // that the current default network disconnected before
+ // the new one connected.
+ if (oldDefaultNetwork != null) {
+ mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
+ oldDefaultNetwork, true);
+ }
+ mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
+ mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
+ notifyLockdownVpn(newNetwork);
+ }
+
if (keep) {
- if (isNewDefault) {
- // Notify system services that this network is up.
- makeDefault(newNetwork);
- synchronized (ConnectivityService.this) {
- // have a new default network, release the transition wakelock in
- // a second if it's held. The second pause is to allow apps
- // to reconnect over the new network
- if (mNetTransitionWakeLock.isHeld()) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
- mNetTransitionWakeLockSerialNumber, 0),
- 1000);
- }
- }
- }
-
- // do this after the default net is switched, but
- // before LegacyTypeTracker sends legacy broadcasts
- for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
-
- if (isNewDefault) {
- // Maintain the illusion: since the legacy API only
- // understands one network at a time, we must pretend
- // that the current default network disconnected before
- // the new one connected.
- if (oldDefaultNetwork != null) {
- mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
- oldDefaultNetwork, true);
- }
- mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
- mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
- notifyLockdownVpn(newNetwork);
- }
-
// Notify battery stats service about this network, both the normal
// interface and any stacked links.
// TODO: Avoid redoing this; this must only be done once when a network comes online.
@@ -4710,4 +4745,11 @@
}
}
}
+
+ @VisibleForTesting
+ public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo nai, NetworkRequest defaultRequest) {
+ return new NetworkMonitor(context, handler, nai, defaultRequest);
+ }
+
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 8a79430..39333f6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -30,6 +30,7 @@
import android.util.SparseArray;
import com.android.internal.util.AsyncChannel;
+import com.android.server.ConnectivityService;
import com.android.server.connectivity.NetworkMonitor;
import java.util.ArrayList;
@@ -51,7 +52,7 @@
// 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
+// If this network can satisfy 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.
@@ -164,7 +165,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest) {
+ NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -172,7 +173,7 @@
linkProperties = lp;
networkCapabilities = nc;
currentScore = score;
- networkMonitor = new NetworkMonitor(context, handler, this, defaultRequest);
+ networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 74ba404..aca6991 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -20,6 +20,7 @@
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.SystemClock;
import android.system.ErrnoException;
@@ -79,6 +80,10 @@
public class NetworkDiagnostics {
private static final String TAG = "NetworkDiagnostics";
+ private static final InetAddress TEST_DNS4 = NetworkUtils.numericToInetAddress("8.8.8.8");
+ private static final InetAddress TEST_DNS6 = NetworkUtils.numericToInetAddress(
+ "2001:4860:4860::8888");
+
// For brevity elsewhere.
private static final long now() {
return SystemClock.elapsedRealtime();
@@ -156,6 +161,21 @@
mStartTime = now();
mDeadlineTime = mStartTime + mTimeoutMs;
+ // Hardcode measurements to TEST_DNS4 and TEST_DNS6 in order to test off-link connectivity.
+ // We are free to modify mLinkProperties with impunity because ConnectivityService passes us
+ // a copy and not the original object. It's easier to do it this way because we don't need
+ // to check whether the LinkProperties already contains these DNS servers because
+ // LinkProperties#addDnsServer checks for duplicates.
+ if (mLinkProperties.isReachable(TEST_DNS4)) {
+ mLinkProperties.addDnsServer(TEST_DNS4);
+ }
+ // TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any
+ // DNS servers for which isReachable() is false, but since this is diagnostic code, be extra
+ // careful.
+ if (mLinkProperties.hasGlobalIPv6Address() || mLinkProperties.hasIPv6DefaultRoute()) {
+ mLinkProperties.addDnsServer(TEST_DNS6);
+ }
+
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (route.hasGateway()) {
prepareIcmpMeasurement(route.getGateway());
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 9994964..696f106 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -81,6 +81,7 @@
import android.util.Log;
import android.util.LogPrinter;
+import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import org.mockito.ArgumentCaptor;
@@ -118,7 +119,7 @@
private INetworkPolicyManager mPolicyService;
private BroadcastInterceptingContext mServiceContext;
- private ConnectivityService mService;
+ private WrappedConnectivityService mService;
private ConnectivityManager mCm;
private MockNetworkAgent mWiFiNetworkAgent;
private MockNetworkAgent mCellNetworkAgent;
@@ -148,6 +149,7 @@
}
private class MockNetworkAgent {
+ private final WrappedNetworkMonitor mWrappedNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
private final Thread mThread;
@@ -172,6 +174,7 @@
throw new UnsupportedOperationException("unimplemented network type");
}
final ConditionVariable initComplete = new ConditionVariable();
+ final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV();
mThread = new Thread() {
public void run() {
Looper.prepare();
@@ -186,6 +189,8 @@
};
mThread.start();
waitFor(initComplete);
+ waitFor(networkMonitorAvailable);
+ mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
}
public void adjustScore(int change) {
@@ -211,44 +216,46 @@
assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE);
assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
- // To pretend network is validated, we transition it to the CONNECTED state without
- // NET_CAPABILITY_INTERNET so NetworkMonitor doesn't bother trying to validate and
- // just rubber stamps it as validated. Afterwards we add NET_CAPABILITY_INTERNET so
- // the network can satisfy the default request.
NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- // If we connect a network without INTERNET capability, it'll get reaped.
- // Prevent the reaping by adding a NetworkRequest.
+ mWrappedNetworkMonitor.gen204ProbeResult = 204;
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(mNetworkCapabilities.getTransportTypes()[0])
.build();
callback = new NetworkCallback() {
public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ if (network.equals(getNetwork()) &&
+ networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
validatedCv.open();
}
}
};
- mCm.requestNetwork(request, callback);
- } else {
- mNetworkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ mCm.registerNetworkCallback(request, callback);
}
+ addCapability(NET_CAPABILITY_INTERNET);
connectWithoutInternet();
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- mNetworkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ mWrappedNetworkMonitor.gen204ProbeResult = 500;
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
+ public void connectWithCaptivePortal() {
+ mWrappedNetworkMonitor.gen204ProbeResult = 200;
+ connect(false);
+ waitFor(new Criteria() { public boolean get() {
+ NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork());
+ return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} });
+ mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ }
+
public void disconnect() {
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -261,14 +268,18 @@
public ConditionVariable getDisconnectedCV() {
return mDisconnected;
}
+
+ public WrappedNetworkMonitor getWrappedNetworkMonitor() {
+ return mWrappedNetworkMonitor;
+ }
}
private static class MockNetworkFactory extends NetworkFactory {
- final ConditionVariable mNetworkStartedCV = new ConditionVariable();
- final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
- final ConditionVariable mNetworkRequestedCV = new ConditionVariable();
- final ConditionVariable mNetworkReleasedCV = new ConditionVariable();
- final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
+ private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkRequestedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkReleasedCV = new ConditionVariable();
+ private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
public MockNetworkFactory(Looper looper, Context context, String logTag,
NetworkCapabilities filter) {
@@ -328,7 +339,26 @@
}
}
+ // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
+ private class WrappedNetworkMonitor extends NetworkMonitor {
+ // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
+ public int gen204ProbeResult = 500;
+
+ public WrappedNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest) {
+ super(context, handler, networkAgentInfo, defaultRequest);
+ }
+
+ @Override
+ protected int isCaptivePortal() {
+ return gen204ProbeResult;
+ }
+ }
+
private class WrappedConnectivityService extends ConnectivityService {
+ private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable();
+ private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
super(context, netManager, statsService, policyManager);
@@ -360,6 +390,25 @@
return netId;
}
}
+
+ @Override
+ public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo nai, NetworkRequest defaultRequest) {
+ final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai,
+ defaultRequest);
+ mLastCreatedNetworkMonitor = monitor;
+ mNetworkMonitorCreated.open();
+ return monitor;
+ }
+
+ public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
+ return mLastCreatedNetworkMonitor;
+ }
+
+ public ConditionVariable getNetworkMonitorCreatedCV() {
+ mNetworkMonitorCreated.close();
+ return mNetworkMonitorCreated;
+ }
}
private interface Criteria {
@@ -414,7 +463,7 @@
mService = new WrappedConnectivityService(
mServiceContext, mNetManager, mStatsService, mPolicyService);
mService.systemReady();
- mCm = new ConnectivityManager(mService);
+ mCm = new ConnectivityManager(getContext(), mService);
}
private int transportToLegacyType(int transport) {
@@ -586,29 +635,29 @@
@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.
+ // Test bringing up unvalidated WiFi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- cv = waitForConnectivityBroadcasts(2);
- mWiFiNetworkAgent.connect(true);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(false);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
- assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
NET_CAPABILITY_VALIDATED));
- // Test WiFi disconnect.
+ // Test bringing up validated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
cv = waitForConnectivityBroadcasts(2);
- mWiFiNetworkAgent.disconnect();
+ mCellNetworkAgent.connect(true);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_CELLULAR);
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ // Test cellular disconnect.
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
// Unlingering a network should not cause it to be marked as validated.
- assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
NET_CAPABILITY_VALIDATED));
}
@@ -846,12 +895,8 @@
cellCv = cellNetworkCallback.getConditionVariable();
wifiCv = wifiNetworkCallback.getConditionVariable();
- // Our method for faking successful validation generates an additional callback, so wait
- // for broadcast instead.
- cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- waitFor(cv);
waitFor(cellCv);
assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
@@ -868,12 +913,8 @@
cellCv = cellNetworkCallback.getConditionVariable();
wifiCv = wifiNetworkCallback.getConditionVariable();
- // Our method for faking successful validation generates an additional callback, so wait
- // for broadcast instead.
- cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- waitFor(cv);
waitFor(wifiCv);
assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
waitFor(cellCv);
@@ -1088,6 +1129,63 @@
verifyActiveNetwork(TRANSPORT_CELLULAR);
}
+ @LargeTest
+ public void testCaptivePortal() {
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+ final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_VALIDATED).build();
+ mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+ ConditionVariable validatedCv = validatedCallback.getConditionVariable();
+
+ // Bring up a network with a captive portal.
+ // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+ ConditionVariable cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithCaptivePortal();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+
+ // Take down network.
+ // Expect onLost callback.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+
+ // Bring up a network with a captive portal.
+ // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithCaptivePortal();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+
+ // Make captive portal disappear then revalidate.
+ // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ waitFor(cv);
+ assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+
+ // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
+ waitFor(validatedCv);
+ assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback());
+
+ // Break network connectivity.
+ // Expect NET_CAPABILITY_VALIDATED onLost callback.
+ validatedCv = validatedCallback.getConditionVariable();
+ mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ waitFor(validatedCv);
+ assertEquals(CallbackState.LOST, validatedCallback.getLastCallback());
+ }
+
// @Override
// public void tearDown() throws Exception {
// super.tearDown();