summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--api/system-current.txt4
-rw-r--r--api/test-current.txt5
-rw-r--r--core/java/android/net/NetworkCapabilities.java109
-rw-r--r--core/java/android/net/NetworkRequest.java35
-rw-r--r--packages/CaptivePortalLogin/AndroidManifest.xml4
-rw-r--r--packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java12
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml3
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java344
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkNotificationManager.java19
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java27
-rw-r--r--tests/net/java/android/net/NetworkCapabilitiesTest.java91
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java348
13 files changed, 842 insertions, 161 deletions
diff --git a/api/current.txt b/api/current.txt
index 5974877f153d..5b15e6f040b8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26057,6 +26057,8 @@ package android.net {
public class NetworkRequest implements android.os.Parcelable {
method public int describeContents();
+ method public boolean hasCapability(int);
+ method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR;
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 863bfdf684b7..5a4fef13b1d0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2636,6 +2636,10 @@ package android.net {
field public static final int ERROR_INVALID_NETWORK = 1; // 0x1
}
+ public final class NetworkCapabilities implements android.os.Parcelable {
+ field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+ }
+
public class NetworkKey implements android.os.Parcelable {
ctor public NetworkKey(android.net.WifiKey);
method public int describeContents();
diff --git a/api/test-current.txt b/api/test-current.txt
index 24d1275ade80..24c22dfd2da9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -253,6 +253,11 @@ package android.net {
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public final class NetworkCapabilities implements android.os.Parcelable {
+ method public int[] getCapabilities();
+ method public int[] getTransportTypes();
+ }
+
public class TrafficStats {
method public static long getLoopbackRxBytes();
method public static long getLoopbackRxPackets();
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 01546b0001ef..d50d43104eb8 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -17,6 +17,8 @@
package android.net;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.net.ConnectivityManager.NetworkCallback;
import android.os.Parcel;
import android.os.Parcelable;
@@ -61,15 +63,7 @@ public final class NetworkCapabilities implements Parcelable {
public NetworkCapabilities(NetworkCapabilities nc) {
if (nc != null) {
- mNetworkCapabilities = nc.mNetworkCapabilities;
- mTransportTypes = nc.mTransportTypes;
- mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
- mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
- mNetworkSpecifier = nc.mNetworkSpecifier;
- mSignalStrength = nc.mSignalStrength;
- mUids = nc.mUids;
- mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
- mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
+ set(nc);
}
}
@@ -85,6 +79,24 @@ public final class NetworkCapabilities implements Parcelable {
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null;
mEstablishingVpnAppUid = INVALID_UID;
+ mSSID = null;
+ }
+
+ /**
+ * Set all contents of this object to the contents of a NetworkCapabilities.
+ * @hide
+ */
+ public void set(NetworkCapabilities nc) {
+ mNetworkCapabilities = nc.mNetworkCapabilities;
+ mTransportTypes = nc.mTransportTypes;
+ mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
+ mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+ mNetworkSpecifier = nc.mNetworkSpecifier;
+ mSignalStrength = nc.mSignalStrength;
+ setUids(nc.mUids); // Will make the defensive copy
+ mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
+ mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
+ mSSID = nc.mSSID;
}
/**
@@ -276,6 +288,7 @@ public final class NetworkCapabilities implements Parcelable {
* this network can be used by system apps to upload telemetry data.
* @hide
*/
+ @SystemApi
public static final int NET_CAPABILITY_OEM_PAID = 22;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
@@ -424,6 +437,7 @@ public final class NetworkCapabilities implements Parcelable {
* @return an array of capability values for this instance.
* @hide
*/
+ @TestApi
public @NetCapability int[] getCapabilities() {
return BitUtils.unpackBits(mNetworkCapabilities);
}
@@ -688,6 +702,7 @@ public final class NetworkCapabilities implements Parcelable {
* @return an array of transport type values for this instance.
* @hide
*/
+ @TestApi
public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
@@ -921,7 +936,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Sets the signal strength. This is a signed integer, with higher values indicating a stronger
* signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units
- * reported by WifiManager.
+ * reported by wifi code.
* <p>
* Note that when used to register a network callback, this specifies the minimum acceptable
* signal strength. When received as the state of an existing network it specifies the current
@@ -1053,7 +1068,7 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
- * Tests if the set of UIDs that this network applies to is the same of the passed set of UIDs.
+ * Tests if the set of UIDs that this network applies to is the same as the passed network.
* <p>
* This test only checks whether equal range objects are in both sets. It will
* return false if the ranges are not exactly the same, even if the covered UIDs
@@ -1143,6 +1158,62 @@ public final class NetworkCapabilities implements Parcelable {
mUids.addAll(nc.mUids);
}
+
+ /**
+ * The SSID of the network, or null if not applicable or unknown.
+ * <p>
+ * This is filled in by wifi code.
+ * @hide
+ */
+ private String mSSID;
+
+ /**
+ * Sets the SSID of this network.
+ * @hide
+ */
+ public NetworkCapabilities setSSID(String ssid) {
+ mSSID = ssid;
+ return this;
+ }
+
+ /**
+ * Gets the SSID of this network, or null if none or unknown.
+ * @hide
+ */
+ public String getSSID() {
+ return mSSID;
+ }
+
+ /**
+ * Tests if the SSID of this network is the same as the SSID of the passed network.
+ * @hide
+ */
+ public boolean equalsSSID(NetworkCapabilities nc) {
+ return Objects.equals(mSSID, nc.mSSID);
+ }
+
+ /**
+ * Check if the SSID requirements of this object are matched by the passed object.
+ * @hide
+ */
+ public boolean satisfiedBySSID(NetworkCapabilities nc) {
+ return mSSID == null || mSSID.equals(nc.mSSID);
+ }
+
+ /**
+ * Combine SSIDs of the capabilities.
+ * <p>
+ * This is only legal if either the SSID of this object is null, or both SSIDs are
+ * equal.
+ * @hide
+ */
+ private void combineSSIDs(NetworkCapabilities nc) {
+ if (mSSID != null && !mSSID.equals(nc.mSSID)) {
+ throw new IllegalStateException("Can't combine two SSIDs");
+ }
+ setSSID(nc.mSSID);
+ }
+
/**
* Combine a set of Capabilities to this one. Useful for coming up with the complete set.
* <p>
@@ -1158,6 +1229,7 @@ public final class NetworkCapabilities implements Parcelable {
combineSpecifiers(nc);
combineSignalStrength(nc);
combineUids(nc);
+ combineSSIDs(nc);
}
/**
@@ -1176,7 +1248,8 @@ public final class NetworkCapabilities implements Parcelable {
&& (onlyImmutable || satisfiedByLinkBandwidths(nc))
&& satisfiedBySpecifier(nc)
&& (onlyImmutable || satisfiedBySignalStrength(nc))
- && (onlyImmutable || satisfiedByUids(nc)));
+ && (onlyImmutable || satisfiedByUids(nc))
+ && (onlyImmutable || satisfiedBySSID(nc)));
}
/**
@@ -1265,7 +1338,8 @@ public final class NetworkCapabilities implements Parcelable {
&& equalsLinkBandwidths(that)
&& equalsSignalStrength(that)
&& equalsSpecifier(that)
- && equalsUids(that));
+ && equalsUids(that)
+ && equalsSSID(that));
}
@Override
@@ -1280,7 +1354,8 @@ public final class NetworkCapabilities implements Parcelable {
+ (mLinkDownBandwidthKbps * 19)
+ Objects.hashCode(mNetworkSpecifier) * 23
+ (mSignalStrength * 29)
- + Objects.hashCode(mUids) * 31;
+ + Objects.hashCode(mUids) * 31
+ + Objects.hashCode(mSSID) * 37;
}
@Override
@@ -1297,6 +1372,7 @@ public final class NetworkCapabilities implements Parcelable {
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
dest.writeInt(mSignalStrength);
dest.writeArraySet(mUids);
+ dest.writeString(mSSID);
}
public static final Creator<NetworkCapabilities> CREATOR =
@@ -1314,6 +1390,7 @@ public final class NetworkCapabilities implements Parcelable {
netCap.mSignalStrength = in.readInt();
netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
null /* ClassLoader, null for default */);
+ netCap.mSSID = in.readString();
return netCap;
}
@Override
@@ -1364,6 +1441,10 @@ public final class NetworkCapabilities implements Parcelable {
sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid);
}
+ if (null != mSSID) {
+ sb.append(" SSID: ").append(mSSID);
+ }
+
sb.append("]");
return sb.toString();
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 1ee0ed7d90fc..f3669fb3e25f 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -17,6 +17,8 @@
package android.net;
import android.annotation.NonNull;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkCapabilities.Transport;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -165,9 +167,6 @@ public class NetworkRequest implements Parcelable {
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
* satisfied.
- * <p>
- * If the given capability was previously added to the list of unwanted capabilities
- * then the capability will also be removed from the list of unwanted capabilities.
*
* @param capability The capability to add.
* @return The builder to facilitate chaining
@@ -179,8 +178,7 @@ public class NetworkRequest implements Parcelable {
}
/**
- * Removes (if found) the given capability from this builder instance from both required
- * and unwanted capabilities lists.
+ * Removes (if found) the given capability from this builder instance.
*
* @param capability The capability to remove.
* @return The builder to facilitate chaining.
@@ -199,8 +197,7 @@ public class NetworkRequest implements Parcelable {
* @hide
*/
public Builder setCapabilities(NetworkCapabilities nc) {
- mNetworkCapabilities.clearAll();
- mNetworkCapabilities.combineCapabilities(nc);
+ mNetworkCapabilities.set(nc);
return this;
}
@@ -228,6 +225,7 @@ public class NetworkRequest implements Parcelable {
*
* @param capability The capability to add to unwanted capability list.
* @return The builder to facilitate chaining.
+ *
* @hide
*/
public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
@@ -426,6 +424,29 @@ public class NetworkRequest implements Parcelable {
return type == Type.BACKGROUND_REQUEST;
}
+ /**
+ * @see Builder#addCapability(int)
+ */
+ public boolean hasCapability(@NetCapability int capability) {
+ return networkCapabilities.hasCapability(capability);
+ }
+
+ /**
+ * @see Builder#addUnwantedCapability(int)
+ *
+ * @hide
+ */
+ public boolean hasUnwantedCapability(@NetCapability int capability) {
+ return networkCapabilities.hasUnwantedCapability(capability);
+ }
+
+ /**
+ * @see Builder#addTransportType(int)
+ */
+ public boolean hasTransport(@Transport int transportType) {
+ return networkCapabilities.hasTransport(transportType);
+ }
+
public String toString() {
return "NetworkRequest [ " + type + " id=" + requestId +
(legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index e01e95b927fe..9ecaa03d6e53 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -23,8 +23,10 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
+ <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
- <application android:label="@string/app_name" >
+ <application android:label="@string/app_name"
+ android:usesCleartextTraffic="true">
<activity
android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
android:label="@string/action_bar_label"
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 495d8891a947..3630005416be 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -29,17 +29,18 @@ import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeSpec;
import android.net.dns.ResolvUtil;
import android.net.http.SslError;
+import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.widget.SwipeRefreshLayout;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TypedValue;
@@ -556,15 +557,12 @@ public class CaptivePortalLoginActivity extends Activity {
}
private String getHeaderTitle() {
- NetworkInfo info = mCm.getNetworkInfo(mNetwork);
- if (info == null) {
- return getString(R.string.action_bar_label);
- }
NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
- if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ if (nc == null || TextUtils.isEmpty(nc.getSSID())
+ || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return getString(R.string.action_bar_label);
}
- return getString(R.string.action_bar_title, info.getExtraInfo().replaceAll("^\"|\"$", ""));
+ return getString(R.string.action_bar_title, WifiInfo.removeDoubleQuotes(nc.getSSID()));
}
private String getHeaderSubtitle(URL url) {
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 425df7183aa5..4d9aaecb4092 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -30,7 +30,8 @@
<application
android:label="@string/app_name"
- android:directBootAware="true">
+ android:directBootAware="true"
+ android:usesCleartextTraffic="true">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
<intent-filter>
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9838de1474d0..2593690e4b08 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -102,6 +102,8 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -234,8 +236,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
private KeyStore mKeyStore;
+ @VisibleForTesting
@GuardedBy("mVpns")
- private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+ protected final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
// TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
// a direct call to LockdownVpnTracker.isEnabled().
@@ -494,24 +497,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
private static final int MAX_VALIDATION_LOGS = 10;
private static class ValidationLog {
final Network mNetwork;
- final String mNetworkExtraInfo;
+ final String mName;
final ReadOnlyLocalLog mLog;
- ValidationLog(Network network, String networkExtraInfo, ReadOnlyLocalLog log) {
+ ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
mNetwork = network;
- mNetworkExtraInfo = networkExtraInfo;
+ mName = name;
mLog = log;
}
}
private final ArrayDeque<ValidationLog> mValidationLogs =
new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
- private void addValidationLogs(ReadOnlyLocalLog log, Network network, String networkExtraInfo) {
+ private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
synchronized (mValidationLogs) {
while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
mValidationLogs.removeLast();
}
- mValidationLogs.addFirst(new ValidationLog(network, networkExtraInfo, log));
+ mValidationLogs.addFirst(new ValidationLog(network, name, log));
}
}
@@ -898,6 +901,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
deps);
}
+ private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
+ final NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
+ netCap.setSingleUid(uid);
+ return netCap;
+ }
+
private NetworkRequest createDefaultInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -1150,12 +1162,20 @@ public class ConnectivityService extends IConnectivityManager.Stub
int vpnNetId = NETID_UNSET;
synchronized (mVpns) {
final Vpn vpn = mVpns.get(user);
+ // TODO : now that capabilities contain the UID, the appliesToUid test should
+ // be removed as the satisfying test below should be enough.
if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();
}
NetworkAgentInfo nai;
if (vpnNetId != NETID_UNSET) {
nai = getNetworkAgentInfoForNetId(vpnNetId);
- if (nai != null) return nai.network;
+ if (nai != null) {
+ final NetworkCapabilities requiredCaps =
+ createDefaultNetworkCapabilitiesForUid(uid);
+ if (requiredCaps.satisfiedByNetworkCapabilities(nai.networkCapabilities)) {
+ return nai.network;
+ }
+ }
}
nai = getDefaultNetwork();
if (nai != null
@@ -1352,7 +1372,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nai != null) {
synchronized (nai) {
if (nai.networkCapabilities != null) {
- return networkCapabilitiesWithoutUidsUnlessAllowed(nai.networkCapabilities,
+ return networkCapabilitiesRestrictedForCallerPermissions(
+ nai.networkCapabilities,
Binder.getCallingPid(), Binder.getCallingUid());
}
}
@@ -1366,10 +1387,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
}
- private NetworkCapabilities networkCapabilitiesWithoutUidsUnlessAllowed(
+ private NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
NetworkCapabilities nc, int callerPid, int callerUid) {
- if (checkSettingsPermission(callerPid, callerUid)) return new NetworkCapabilities(nc);
- return new NetworkCapabilities(nc).setUids(null);
+ final NetworkCapabilities newNc = new NetworkCapabilities(nc);
+ if (!checkSettingsPermission(callerPid, callerUid)) {
+ newNc.setUids(null);
+ newNc.setSSID(null);
+ }
+ return newNc;
}
private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
@@ -1415,7 +1440,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
- final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
+ final int uid = Binder.getCallingUid();
+ final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
@@ -1737,6 +1763,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
ni != null ? ni.getState().toString() : "?");
} catch (RemoteException e) {
}
+ intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
}
try {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options);
@@ -2032,7 +2059,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
synchronized (mValidationLogs) {
pw.println("mValidationLogs (most recent first):");
for (ValidationLog p : mValidationLogs) {
- pw.println(p.mNetwork + " - " + p.mNetworkExtraInfo);
+ pw.println(p.mNetwork + " - " + p.mName);
pw.increaseIndent();
p.mLog.dump(fd, pw, args);
pw.decreaseIndent();
@@ -2377,94 +2404,107 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ // This is a no-op if it's called with a message designating a network that has
+ // already been destroyed, because its reference will not be found in the relevant
+ // maps.
private void handleAsyncChannelDisconnected(Message msg) {
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai != null) {
- if (DBG) {
- 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
- // because they lost all their requests or because their score isn't good)
- // then they would disconnect organically, report their new state and then
- // disconnect the channel.
- if (nai.networkInfo.isConnected()) {
- nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
- null, null);
- }
- final boolean wasDefault = isDefaultNetwork(nai);
- if (wasDefault) {
- mDefaultInetConditionPublished = 0;
- // Log default network disconnection before required book-keeping.
- // Let rematchAllNetworksAndRequests() below record a new default network event
- // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
- // whose timestamps tell how long it takes to recover a default network.
- long now = SystemClock.elapsedRealtime();
- metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
- }
- notifyIfacesChangedForNetworkStats();
- // 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);
- mKeepaliveTracker.handleStopAllKeepalives(nai,
- ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
- for (String iface : nai.linkProperties.getAllInterfaceNames()) {
- // Disable wakeup packet monitoring for each interface.
- wakeupModifyInterface(iface, nai.networkCapabilities, false);
- }
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
- mNetworkAgentInfos.remove(msg.replyTo);
- nai.maybeStopClat();
- synchronized (mNetworkForNetId) {
- // Remove the NetworkAgent, but don't mark the netId as
- // available until we've told netd to delete it below.
- mNetworkForNetId.remove(nai.network.netId);
- }
- // Remove all previously satisfied requests.
- for (int i = 0; i < nai.numNetworkRequests(); i++) {
- NetworkRequest request = nai.requestAt(i);
- NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
- if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
- clearNetworkForRequest(request.requestId);
- sendUpdatedScoreToFactories(request, 0);
- }
- }
- nai.clearLingerState();
- if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
- removeDataActivityTracking(nai);
- notifyLockdownVpn(nai);
- ensureNetworkTransitionWakelock(nai.name());
- }
- mLegacyTypeTracker.remove(nai, wasDefault);
- rematchAllNetworksAndRequests(null, 0);
- mLingerMonitor.noteDisconnect(nai);
- if (nai.created) {
- // Tell netd to clean up the configuration for this network
- // (routing rules, DNS, etc).
- // This may be slow as it requires a lot of netd shelling out to ip and
- // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
- // after we've rematched networks with requests which should make a potential
- // fallback network the default or requested a new network from the
- // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
- // long time.
- try {
- mNetd.removeNetwork(nai.network.netId);
- } catch (Exception e) {
- loge("Exception removing network: " + e);
- }
- mDnsManager.removeNetwork(nai.network);
- }
- synchronized (mNetworkForNetId) {
- mNetIdInUse.delete(nai.network.netId);
- }
+ disconnectAndDestroyNetwork(nai);
} else {
NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
}
}
+ // Destroys a network, remove references to it from the internal state managed by
+ // ConnectivityService, free its interfaces and clean up.
+ // Must be called on the Handler thread.
+ private void disconnectAndDestroyNetwork(NetworkAgentInfo nai) {
+ if (DBG) {
+ 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
+ // because they lost all their requests or because their score isn't good)
+ // then they would disconnect organically, report their new state and then
+ // disconnect the channel.
+ if (nai.networkInfo.isConnected()) {
+ nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
+ null, null);
+ }
+ final boolean wasDefault = isDefaultNetwork(nai);
+ if (wasDefault) {
+ mDefaultInetConditionPublished = 0;
+ // Log default network disconnection before required book-keeping.
+ // Let rematchAllNetworksAndRequests() below record a new default network event
+ // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
+ // whose timestamps tell how long it takes to recover a default network.
+ long now = SystemClock.elapsedRealtime();
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
+ }
+ notifyIfacesChangedForNetworkStats();
+ // 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);
+ mKeepaliveTracker.handleStopAllKeepalives(nai,
+ ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+ for (String iface : nai.linkProperties.getAllInterfaceNames()) {
+ // Disable wakeup packet monitoring for each interface.
+ wakeupModifyInterface(iface, nai.networkCapabilities, false);
+ }
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ mNetworkAgentInfos.remove(nai.messenger);
+ nai.maybeStopClat();
+ synchronized (mNetworkForNetId) {
+ // Remove the NetworkAgent, but don't mark the netId as
+ // available until we've told netd to delete it below.
+ mNetworkForNetId.remove(nai.network.netId);
+ }
+ // Remove all previously satisfied requests.
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest request = nai.requestAt(i);
+ NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
+ if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
+ clearNetworkForRequest(request.requestId);
+ sendUpdatedScoreToFactories(request, 0);
+ }
+ }
+ nai.clearLingerState();
+ if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
+ removeDataActivityTracking(nai);
+ notifyLockdownVpn(nai);
+ ensureNetworkTransitionWakelock(nai.name());
+ }
+ mLegacyTypeTracker.remove(nai, wasDefault);
+ if (!nai.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+ updateAllVpnsCapabilities();
+ }
+ rematchAllNetworksAndRequests(null, 0);
+ mLingerMonitor.noteDisconnect(nai);
+ if (nai.created) {
+ // Tell netd to clean up the configuration for this network
+ // (routing rules, DNS, etc).
+ // This may be slow as it requires a lot of netd shelling out to ip and
+ // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
+ // after we've rematched networks with requests which should make a potential
+ // fallback network the default or requested a new network from the
+ // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+ // long time.
+ try {
+ mNetd.removeNetwork(nai.network.netId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ }
+ mDnsManager.removeNetwork(nai.network);
+ }
+ synchronized (mNetworkForNetId) {
+ mNetIdInUse.delete(nai.network.netId);
+ }
+ }
+
// If this method proves to be too slow then we can maintain a separate
// pendingIntent => NetworkRequestInfo map.
// This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
@@ -3707,6 +3747,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ /**
+ * Ask all VPN objects to recompute and update their capabilities.
+ *
+ * When underlying networks change, VPNs may have to update capabilities to reflect things
+ * like the metered bit, their transports, and so on. This asks the VPN objects to update
+ * their capabilities, and as this will cause them to send messages to the ConnectivityService
+ * handler thread through their agent, this is asynchronous. When the capabilities objects
+ * are computed they will be up-to-date as they are computed synchronously from here and
+ * this is running on the ConnectivityService thread.
+ * TODO : Fix this and call updateCapabilities inline to remove out-of-order events.
+ */
+ private void updateAllVpnsCapabilities() {
+ synchronized (mVpns) {
+ for (int i = 0; i < mVpns.size(); i++) {
+ final Vpn vpn = mVpns.valueAt(i);
+ vpn.updateCapabilities();
+ }
+ }
+ }
+
@Override
public boolean updateLockdownVpn() {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
@@ -4181,6 +4241,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ // This checks that the passed capabilities either do not request a specific SSID, or the
+ // calling app has permission to do so.
+ private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
+ int callerPid, int callerUid) {
+ if (null != nc.getSSID() && !checkSettingsPermission(callerPid, callerUid)) {
+ throw new SecurityException("Insufficient permissions to request a specific SSID");
+ }
+ }
+
private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
final SortedSet<Integer> thresholds = new TreeSet();
synchronized (nai) {
@@ -4238,8 +4307,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// the default network request. This allows callers to keep track of
// the system default network.
if (type == NetworkRequest.Type.TRACK_DEFAULT) {
- networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
- networkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
+ networkCapabilities = createDefaultNetworkCapabilitiesForUid(Binder.getCallingUid());
enforceAccessPermission();
} else {
networkCapabilities = new NetworkCapabilities(networkCapabilities);
@@ -4250,6 +4318,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceMeteredApnPolicy(networkCapabilities);
}
ensureRequestableCapabilities(networkCapabilities);
+ ensureSufficientPermissionsForRequest(networkCapabilities,
+ Binder.getCallingPid(), Binder.getCallingUid());
// Set the UID range for this request to the single UID of the requester, or to an empty
// set of UIDs if the caller has the appropriate permission and UIDs have not been set.
// This will overwrite any allowed UIDs in the requested capabilities. Though there
@@ -4328,6 +4398,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
+ ensureSufficientPermissionsForRequest(networkCapabilities,
+ Binder.getCallingPid(), Binder.getCallingUid());
ensureValidNetworkSpecifier(networkCapabilities);
restrictRequestUidsForCaller(networkCapabilities);
@@ -4383,6 +4455,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+ ensureSufficientPermissionsForRequest(networkCapabilities,
+ Binder.getCallingPid(), Binder.getCallingUid());
restrictRequestUidsForCaller(nc);
// Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
// make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
@@ -4409,6 +4483,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceAccessPermission();
}
ensureValidNetworkSpecifier(networkCapabilities);
+ ensureSufficientPermissionsForRequest(networkCapabilities,
+ Binder.getCallingPid(), Binder.getCallingUid());
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
restrictRequestUidsForCaller(nc);
@@ -4541,8 +4617,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
}
- addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network,
- networkInfo.getExtraInfo());
+ final String extraInfo = networkInfo.getExtraInfo();
+ final String name = TextUtils.isEmpty(extraInfo)
+ ? nai.networkCapabilities.getSSID() : extraInfo;
+ addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
if (DBG) log("registerNetworkAgent " + nai);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
return nai.network.netId;
@@ -4562,7 +4640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
- LinkProperties newLp = networkAgent.linkProperties;
+ LinkProperties newLp = new LinkProperties(networkAgent.linkProperties);
int netId = networkAgent.network.netId;
// The NetworkAgentInfo does not know whether clatd is running on its network or not. Before
@@ -4596,6 +4674,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
+ synchronized (networkAgent) {
+ networkAgent.linkProperties = newLp;
+ }
notifyIfacesChangedForNetworkStats();
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
@@ -4846,12 +4927,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (!newNc.hasTransport(TRANSPORT_VPN)) {
// Tell VPNs about updated capabilities, since they may need to
// bubble those changes through.
- synchronized (mVpns) {
- for (int i = 0; i < mVpns.size(); i++) {
- final Vpn vpn = mVpns.valueAt(i);
- vpn.updateCapabilities();
- }
- }
+ updateAllVpnsCapabilities();
}
}
@@ -4980,7 +5056,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
// networkAgent can't be null as it has been accessed a few lines above.
- final NetworkCapabilities nc = networkCapabilitiesWithoutUidsUnlessAllowed(
+ final NetworkCapabilities nc = networkCapabilitiesRestrictedForCallerPermissions(
networkAgent.networkCapabilities, nri.mPid, nri.mUid);
putParcelable(bundle, nc);
break;
@@ -5511,6 +5587,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
+ disconnectAndDestroyNetwork(networkAgent);
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
// going into or coming out of SUSPEND: rescore and notify
@@ -5812,4 +5889,61 @@ public class ConnectivityService extends IConnectivityManager.Stub
private static int encodeBool(boolean b) {
return b ? 1 : 0;
}
-}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new ShellCmd()).exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private class ShellCmd extends ShellCommand {
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "airplane-mode":
+ final String action = getNextArg();
+ if ("enable".equals(action)) {
+ setAirplaneMode(true);
+ return 0;
+ } else if ("disable".equals(action)) {
+ setAirplaneMode(false);
+ return 0;
+ } else if (action == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ final int enabled = Settings.Global.getInt(cr,
+ Settings.Global.AIRPLANE_MODE_ON);
+ pw.println(enabled == 0 ? "disabled" : "enabled");
+ return 0;
+ } else {
+ onHelp();
+ return -1;
+ }
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ pw.println(e);
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Connectivity service commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" airplane-mode [enable|disable]");
+ pw.println(" Turn airplane mode on or off.");
+ pw.println(" airplane-mode");
+ pw.println(" Get airplane mode.");
+ }
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 0d935dba22c6..36a2476d2ceb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -23,8 +23,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -130,16 +132,17 @@ public class NetworkNotificationManager {
final String tag = tagFor(id);
final int eventId = notifyType.eventId;
final int transportType;
- final String extraInfo;
+ final String name;
if (nai != null) {
transportType = getFirstTransportType(nai);
- extraInfo = nai.networkInfo.getExtraInfo();
+ final String extraInfo = nai.networkInfo.getExtraInfo();
+ name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSSID() : extraInfo;
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
} else {
// Legacy notifications.
transportType = TRANSPORT_CELLULAR;
- extraInfo = null;
+ name = null;
}
// Clear any previous notification with lower priority, otherwise return. http://b/63676954.
@@ -156,9 +159,8 @@ public class NetworkNotificationManager {
if (DBG) {
Slog.d(TAG, String.format(
- "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
- tag, nameOf(eventId), getTransportName(transportType), extraInfo,
- highPriority));
+ "showNotification tag=%s event=%s transport=%s name=%s highPriority=%s",
+ tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
}
Resources r = Resources.getSystem();
@@ -176,7 +178,8 @@ public class NetworkNotificationManager {
switch (transportType) {
case TRANSPORT_WIFI:
title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
break;
case TRANSPORT_CELLULAR:
title = r.getString(R.string.network_available_sign_in, 0);
@@ -186,7 +189,7 @@ public class NetworkNotificationManager {
break;
default:
title = r.getString(R.string.network_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
+ details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
}
} else if (notifyType == NotificationType.NETWORK_SWITCH) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index df94de2e52d1..bce735b547ef 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -172,10 +172,13 @@ public class Vpn {
private PendingIntent mStatusIntent;
private volatile boolean mEnableTeardown = true;
private final INetworkManagementService mNetd;
- private VpnConfig mConfig;
- private NetworkAgent mNetworkAgent;
+ @VisibleForTesting
+ protected VpnConfig mConfig;
+ @VisibleForTesting
+ protected NetworkAgent mNetworkAgent;
private final Looper mLooper;
- private final NetworkCapabilities mNetworkCapabilities;
+ @VisibleForTesting
+ protected final NetworkCapabilities mNetworkCapabilities;
private final SystemServices mSystemServices;
/**
@@ -316,15 +319,12 @@ public class Vpn {
boolean roaming = false;
boolean congested = false;
- if (ArrayUtils.isEmpty(underlyingNetworks)) {
- // No idea what the underlying networks are; assume sane defaults
- metered = true;
- roaming = false;
- congested = false;
- } else {
+ boolean hadUnderlyingNetworks = false;
+ if (null != underlyingNetworks) {
for (Network underlying : underlyingNetworks) {
final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
if (underlyingCaps == null) continue;
+ hadUnderlyingNetworks = true;
for (int underlyingType : underlyingCaps.getTransportTypes()) {
transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
}
@@ -340,6 +340,12 @@ public class Vpn {
congested |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_CONGESTED);
}
}
+ if (!hadUnderlyingNetworks) {
+ // No idea what the underlying networks are; assume sane defaults
+ metered = true;
+ roaming = false;
+ congested = false;
+ }
caps.setTransportTypes(transportTypes);
caps.setLinkDownstreamBandwidthKbps(downKbps);
@@ -1071,7 +1077,8 @@ public class Vpn {
// Returns true if the VPN has been established and the calling UID is its owner. Used to check
// that a call to mutate VPN state is admissible.
- private boolean isCallerEstablishedOwnerLocked() {
+ @VisibleForTesting
+ protected boolean isCallerEstablishedOwnerLocked() {
return isRunningLocked() && Binder.getCallingUid() == mOwnerUID;
}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index 06965925e49e..2f4d69ec783a 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -40,12 +40,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,6 +56,9 @@ import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkCapabilitiesTest {
+ private static final String TEST_SSID = "TEST_SSID";
+ private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID";
+
@Test
public void testMaybeMarkCapabilitiesRestricted() {
// verify EIMS is restricted
@@ -268,6 +273,8 @@ public class NetworkCapabilitiesTest {
.addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED);
assertEqualsThroughMarshalling(netCap);
+ netCap.setSSID(TEST_SSID);
+ assertEqualsThroughMarshalling(netCap);
}
@Test
@@ -362,6 +369,27 @@ public class NetworkCapabilitiesTest {
}
@Test
+ public void testSSID() {
+ NetworkCapabilities nc1 = new NetworkCapabilities();
+ NetworkCapabilities nc2 = new NetworkCapabilities();
+ assertTrue(nc2.satisfiedBySSID(nc1));
+
+ nc1.setSSID(TEST_SSID);
+ assertTrue(nc2.satisfiedBySSID(nc1));
+ nc2.setSSID("different " + TEST_SSID);
+ assertFalse(nc2.satisfiedBySSID(nc1));
+
+ assertTrue(nc1.satisfiedByImmutableNetworkCapabilities(nc2));
+ assertFalse(nc1.satisfiedByNetworkCapabilities(nc2));
+ }
+
+ private ArraySet<UidRange> uidRange(int from, int to) {
+ final ArraySet<UidRange> range = new ArraySet<>(1);
+ range.add(new UidRange(from, to));
+ return range;
+ }
+
+ @Test
public void testCombineCapabilities() {
NetworkCapabilities nc1 = new NetworkCapabilities();
NetworkCapabilities nc2 = new NetworkCapabilities();
@@ -382,6 +410,35 @@ public class NetworkCapabilitiesTest {
// will never be satisfied.
assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+
+ nc1.setSSID(TEST_SSID);
+ nc2.combineCapabilities(nc1);
+ assertTrue(TEST_SSID.equals(nc2.getSSID()));
+
+ // Because they now have the same SSID, the following call should not throw
+ nc2.combineCapabilities(nc1);
+
+ nc1.setSSID(DIFFERENT_TEST_SSID);
+ try {
+ nc2.combineCapabilities(nc1);
+ fail("Expected IllegalStateException: can't combine different SSIDs");
+ } catch (IllegalStateException expected) {}
+ nc1.setSSID(TEST_SSID);
+
+ nc1.setUids(uidRange(10, 13));
+ assertNotEquals(nc1, nc2);
+ nc2.combineCapabilities(nc1); // Everything + 10~13 is still everything.
+ assertNotEquals(nc1, nc2);
+ nc1.combineCapabilities(nc2); // 10~13 + everything is everything.
+ assertEquals(nc1, nc2);
+ nc1.setUids(uidRange(10, 13));
+ nc2.setUids(uidRange(20, 23));
+ assertNotEquals(nc1, nc2);
+ nc1.combineCapabilities(nc2);
+ assertTrue(nc1.appliesToUid(12));
+ assertFalse(nc2.appliesToUid(12));
+ assertTrue(nc1.appliesToUid(22));
+ assertTrue(nc2.appliesToUid(22));
}
@Test
@@ -420,4 +477,38 @@ public class NetworkCapabilitiesTest {
p.setDataPosition(0);
assertEquals(NetworkCapabilities.CREATOR.createFromParcel(p), netCap);
}
+
+ @Test
+ public void testSet() {
+ NetworkCapabilities nc1 = new NetworkCapabilities();
+ NetworkCapabilities nc2 = new NetworkCapabilities();
+
+ nc1.addUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ nc1.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ assertNotEquals(nc1, nc2);
+ nc2.set(nc1);
+ assertEquals(nc1, nc2);
+ assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
+
+ // This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
+ nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
+ nc1.setSSID(TEST_SSID);
+ nc2.set(nc1);
+ assertEquals(nc1, nc2);
+ // Contrary to combineCapabilities, set() will have removed the NOT_ROAMING capability
+ // from nc2.
+ assertFalse(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertTrue(TEST_SSID.equals(nc2.getSSID()));
+
+ nc1.setSSID(DIFFERENT_TEST_SSID);
+ nc2.set(nc1);
+ assertEquals(nc1, nc2);
+ assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSSID()));
+
+ nc1.setUids(uidRange(10, 13));
+ nc2.set(nc1); // Overwrites, as opposed to combineCapabilities
+ assertEquals(nc1, nc2);
+ }
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 53c62c2402a4..d6018f1b1300 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -84,6 +84,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.CaptivePortal;
import android.net.ConnectivityManager;
@@ -111,6 +112,7 @@ import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.UidRange;
+import android.net.VpnService;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
import android.net.util.MultinetworkPolicyTracker;
@@ -133,6 +135,7 @@ import android.test.mock.MockContentResolver;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.net.VpnConfig;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -196,6 +199,7 @@ public class ConnectivityServiceTest {
private MockNetworkAgent mWiFiNetworkAgent;
private MockNetworkAgent mCellNetworkAgent;
private MockNetworkAgent mEthernetNetworkAgent;
+ private MockVpn mMockVpn;
private Context mContext;
@Mock IpConnectivityMetrics.Logger mMetricsService;
@@ -477,6 +481,14 @@ public class ConnectivityServiceTest {
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
+ public void setNetworkCapabilities(NetworkCapabilities nc,
+ boolean sendToConnectivityService) {
+ mNetworkCapabilities.set(nc);
+ if (sendToConnectivityService) {
+ mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ }
+ }
+
public void connectWithoutInternet() {
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -506,6 +518,7 @@ public class ConnectivityServiceTest {
mWrappedNetworkMonitor.gen204ProbeResult = 204;
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(mNetworkCapabilities.getTransportTypes()[0])
+ .clearCapabilities()
.build();
callback = new NetworkCallback() {
public void onCapabilitiesChanged(Network network,
@@ -591,6 +604,10 @@ public class ConnectivityServiceTest {
return mRedirectUrl;
}
+ public NetworkAgent getNetworkAgent() {
+ return mNetworkAgent;
+ }
+
public NetworkCapabilities getNetworkCapabilities() {
return mNetworkCapabilities;
}
@@ -723,6 +740,87 @@ public class ConnectivityServiceTest {
}
}
+ private static Looper startHandlerThreadAndReturnLooper() {
+ final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
+ handlerThread.start();
+ return handlerThread.getLooper();
+ }
+
+ private class MockVpn extends Vpn {
+ // TODO : the interactions between this mock and the mock network agent are too
+ // hard to get right at this moment, because it's unclear in which case which
+ // target needs to get a method call or both, and in what order. It's because
+ // MockNetworkAgent wants to manage its own NetworkCapabilities, but the Vpn
+ // parent class of MockVpn agent wants that responsibility.
+ // That being said inside the test it should be possible to make the interactions
+ // harder to get wrong with precise speccing, judicious comments, helper methods
+ // and a few sprinkled assertions.
+
+ private boolean mConnected = false;
+ // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
+ // not inherit from NetworkAgent.
+ private MockNetworkAgent mMockNetworkAgent;
+
+ public MockVpn(int userId) {
+ super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
+ userId);
+ }
+
+ public void setNetworkAgent(MockNetworkAgent agent) {
+ waitForIdle(agent, TIMEOUT_MS);
+ mMockNetworkAgent = agent;
+ mNetworkAgent = agent.getNetworkAgent();
+ mNetworkCapabilities.set(agent.getNetworkCapabilities());
+ }
+
+ public void setUids(Set<UidRange> uids) {
+ mNetworkCapabilities.setUids(uids);
+ updateCapabilities();
+ }
+
+ @Override
+ public int getNetId() {
+ return mMockNetworkAgent.getNetwork().netId;
+ }
+
+ @Override
+ public boolean appliesToUid(int uid) {
+ return mConnected; // Trickery to simplify testing.
+ }
+
+ @Override
+ protected boolean isCallerEstablishedOwnerLocked() {
+ return mConnected; // Similar trickery
+ }
+
+ public void connect() {
+ mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
+ mConnected = true;
+ mConfig = new VpnConfig();
+ }
+
+ @Override
+ public void updateCapabilities() {
+ if (!mConnected) return;
+ super.updateCapabilities();
+ // Because super.updateCapabilities will update the capabilities of the agent but not
+ // the mock agent, the mock agent needs to know about them.
+ copyCapabilitiesToNetworkAgent();
+ }
+
+ private void copyCapabilitiesToNetworkAgent() {
+ if (null != mMockNetworkAgent) {
+ mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities,
+ false /* sendToConnectivityService */);
+ }
+ }
+
+ public void disconnect() {
+ mConnected = false;
+ mConfig = null;
+ }
+ }
+
private class FakeWakeupMessage extends WakeupMessage {
private static final int UNREASONABLY_LONG_WAIT = 1000;
@@ -889,6 +987,17 @@ public class ConnectivityServiceTest {
return mLastCreatedNetworkMonitor;
}
+ public void mockVpn(int uid) {
+ synchronized (mVpns) {
+ int userId = UserHandle.getUserId(uid);
+ mMockVpn = new MockVpn(userId);
+ // This has no effect unless the VPN is actually connected, because things like
+ // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
+ // netId, and check if that network is actually connected.
+ mVpns.put(userId, mMockVpn);
+ }
+ }
+
public void waitForIdle(int timeoutMs) {
waitForIdleHandler(mHandlerThread, timeoutMs);
}
@@ -932,8 +1041,9 @@ public class ConnectivityServiceTest {
mock(INetworkPolicyManager.class),
mock(IpConnectivityLog.class));
- mService.systemReady();
mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
+ mService.systemReady();
+ mService.mockVpn(Process.myUid());
mCm.bindProcessToNetwork(null);
// Ensure that the default setting for Captive Portals is used for most tests
@@ -1346,6 +1456,7 @@ public class ConnectivityServiceTest {
private final static int TIMEOUT_MS = 100;
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
+ private Network mLastAvailableNetwork;
protected void setLastCallback(CallbackState state, Network network, Object o) {
mCallbacks.offer(new CallbackInfo(state, network, o));
@@ -1353,6 +1464,7 @@ public class ConnectivityServiceTest {
@Override
public void onAvailable(Network network) {
+ mLastAvailableNetwork = network;
setLastCallback(CallbackState.AVAILABLE, network, null);
}
@@ -1388,9 +1500,14 @@ public class ConnectivityServiceTest {
@Override
public void onLost(Network network) {
+ mLastAvailableNetwork = null;
setLastCallback(CallbackState.LOST, network, null);
}
+ public Network getLastAvailableNetwork() {
+ return mLastAvailableNetwork;
+ }
+
CallbackInfo nextCallback(int timeoutMs) {
CallbackInfo cb = null;
try {
@@ -1526,7 +1643,8 @@ public class ConnectivityServiceTest {
void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent) {
CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
- assertTrue(fn.test((NetworkCapabilities) cbi.arg));
+ assertTrue("Received capabilities don't match expectations : " + cbi.arg,
+ fn.test((NetworkCapabilities) cbi.arg));
}
void assertNoCallback() {
@@ -1657,6 +1775,7 @@ public class ConnectivityServiceTest {
callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.connect(true);
// We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
@@ -1667,6 +1786,7 @@ public class ConnectivityServiceTest {
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
@@ -1675,11 +1795,13 @@ public class ConnectivityServiceTest {
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
for (int i = 0; i < 4; i++) {
MockNetworkAgent oldNetwork, newNetwork;
@@ -1708,6 +1830,7 @@ public class ConnectivityServiceTest {
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
defaultCallback.assertNoCallback();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// 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).
@@ -1717,8 +1840,11 @@ public class ConnectivityServiceTest {
mWiFiNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ waitForIdle();
+ assertEquals(null, mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(callback);
waitForIdle();
@@ -1735,6 +1861,7 @@ public class ConnectivityServiceTest {
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 20.
// Cell stays up because it would satisfy the default request if it validated.
@@ -1743,12 +1870,14 @@ public class ConnectivityServiceTest {
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 70.
// Cell is lingered because it would not satisfy any request, even if it validated.
@@ -1759,6 +1888,7 @@ public class ConnectivityServiceTest {
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Tear down wifi.
mWiFiNetworkAgent.disconnect();
@@ -1766,6 +1896,7 @@ public class ConnectivityServiceTest {
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
// it's arguably correct to linger it, since it was the default network before it validated.
@@ -1777,6 +1908,7 @@ public class ConnectivityServiceTest {
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -1785,12 +1917,15 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ waitForIdle();
+ assertEquals(null, mCm.getActiveNetwork());
// If a network is lingering, and we add and remove a request from it, resume lingering.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -1798,6 +1933,7 @@ public class ConnectivityServiceTest {
// TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
@@ -1814,6 +1950,7 @@ public class ConnectivityServiceTest {
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Cell is now the default network. Pin it with a cell-specific request.
noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1824,6 +1961,7 @@ public class ConnectivityServiceTest {
mWiFiNetworkAgent.connect(true);
callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// The default request is lingering on cell, but nothing happens to cell, and we send no
// callbacks for it, because it's kept up by cellRequest.
callback.assertNoCallback();
@@ -1847,6 +1985,7 @@ public class ConnectivityServiceTest {
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Let linger run its course.
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
@@ -2495,23 +2634,27 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up wifi and expect CALLBACK_AVAILABLE.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
cellNetworkCallback.assertNoCallback();
defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring down cell. Expect no default network callback, since it wasn't the default.
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
+ assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up cell. Expect no default network callback, since it won't be the default.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
+ assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring down wifi. Expect the default network callback to notified of LOST wifi
// followed by AVAILABLE cell.
@@ -2522,6 +2665,24 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ waitForIdle();
+ assertEquals(null, mCm.getActiveNetwork());
+
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ defaultNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+ assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+
+ vpnNetworkAgent.disconnect();
+ defaultNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ waitForIdle();
+ assertEquals(null, mCm.getActiveNetwork());
}
@Test
@@ -4012,6 +4173,7 @@ public class ConnectivityServiceTest {
final TestNetworkCallback genericNotVpnNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
final NetworkRequest genericRequest = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN).build();
@@ -4024,6 +4186,8 @@ public class ConnectivityServiceTest {
mCm.registerNetworkCallback(genericNotVpnRequest, genericNotVpnNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+ defaultCallback.assertNoCallback();
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
@@ -4031,26 +4195,30 @@ public class ConnectivityServiceTest {
genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
vpnNetworkCallback.assertNoCallback();
-
- // TODO : check callbacks agree with the return value of mCm.getActiveNetwork().
- // Right now this is not possible because establish() is not adequately instrumented
- // in this test.
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
final ArraySet<UidRange> ranges = new ArraySet<>();
ranges.add(new UidRange(uid, uid));
- vpnNetworkAgent.setUids(ranges);
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
vpnNetworkAgent.connect(false);
+ mMockVpn.connect();
genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
ranges.clear();
vpnNetworkAgent.setUids(ranges);
@@ -4060,13 +4228,24 @@ public class ConnectivityServiceTest {
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ // TODO : The default network callback should actually get a LOST call here (also see the
+ // comment below for AVAILABLE). This is because ConnectivityService does not look at UID
+ // ranges at all when determining whether a network should be rematched. In practice, VPNs
+ // can't currently update their UIDs without disconnecting, so this does not matter too
+ // much, but that is the reason the test here has to check for an update to the
+ // capabilities instead of the expected LOST then AVAILABLE.
+ defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+
ranges.add(new UidRange(uid, uid));
- vpnNetworkAgent.setUids(ranges);
+ mMockVpn.setUids(ranges);
genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+ // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
+ // happen outside of the test, ConnectivityService does not rematch callbacks.
+ defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
mWiFiNetworkAgent.disconnect();
@@ -4074,6 +4253,7 @@ public class ConnectivityServiceTest {
genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
vpnNetworkCallback.assertNoCallback();
+ defaultCallback.assertNoCallback();
vpnNetworkAgent.disconnect();
@@ -4081,9 +4261,161 @@ public class ConnectivityServiceTest {
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ assertEquals(null, mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(genericNetworkCallback);
mCm.unregisterNetworkCallback(wifiNetworkCallback);
mCm.unregisterNetworkCallback(vpnNetworkCallback);
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }
+
+ @Test
+ public void testVpnWithAndWithoutInternet() {
+ final int uid = Process.myUid();
+
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+ defaultCallback.assertNoCallback();
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+
+ defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ mMockVpn.connect();
+
+ defaultCallback.assertNoCallback();
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+
+ vpnNetworkAgent.disconnect();
+ defaultCallback.assertNoCallback();
+
+ vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */);
+ mMockVpn.connect();
+ defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+ assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+
+ vpnNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+
+ vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ ranges.clear();
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+ mMockVpn.connect();
+ defaultCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }
+
+ @Test
+ public void testVpnSetUnderlyingNetworks() {
+ final int uid = Process.myUid();
+
+ final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .addTransportType(TRANSPORT_VPN)
+ .build();
+ NetworkCapabilities nc;
+ mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
+ vpnNetworkCallback.assertNoCallback();
+
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.connect();
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+
+ vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+ nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ assertFalse(nc.hasTransport(TRANSPORT_CELLULAR));
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ // For safety reasons a VPN without underlying networks is considered metered.
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+
+ // Connect cell and use it as an underlying network.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ // Don't disconnect, but note the VPN is not using wifi any more.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ // Use Wifi but not cell. Note the VPN is now unmetered.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ // Use both again.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ // Disconnect cell. Receive update without even removing the dead network from the
+ // underlying networks – it's dead anyway. Not metered any more.
+ mCellNetworkAgent.disconnect();
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ // Disconnect wifi too. No underlying networks means this is now metered.
+ mWiFiNetworkAgent.disconnect();
+ vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
+ vpnNetworkAgent);
+
+ mMockVpn.disconnect();
}
}