summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/net/ConnectivityManager.java22
-rw-r--r--core/java/android/net/IConnectivityManager.aidl1
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java70
-rw-r--r--services/core/java/com/android/server/job/controllers/ConnectivityController.java6
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java87
5 files changed, 160 insertions, 26 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d7ecc81ffdba..467eb9b0b0bf 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2505,6 +2505,28 @@ public class ConnectivityManager {
}
/**
+ * Returns if the active data network for the given UID is metered. A network
+ * is classified as metered when the user is sensitive to heavy data usage on
+ * that connection due to monetary costs, data limitations or
+ * battery/performance issues. You should check this before doing large
+ * data transfers, and warn the user or delay the operation until another
+ * network is available.
+ *
+ * @return {@code true} if large transfers should be avoided, otherwise
+ * {@code false}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ public boolean isActiveNetworkMeteredForUid(int uid) {
+ try {
+ return mService.isActiveNetworkMeteredForUid(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* If the LockdownVpn mechanism is enabled, updates the vpn
* with a reload of its profile.
*
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index a6fe7389bc72..f11372c2b31c 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -66,6 +66,7 @@ interface IConnectivityManager
NetworkQuotaInfo getActiveNetworkQuotaInfo();
boolean isActiveNetworkMetered();
+ boolean isActiveNetworkMeteredForUid(int uid);
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c1801b80af0d..6e8c0d4a55c1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -969,7 +969,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (!mLockdownEnabled) {
int user = UserHandle.getUserId(uid);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(user);
+ Vpn vpn = getVpn(user);
if (vpn != null && vpn.appliesToUid(uid)) {
return vpn.getUnderlyingNetworks();
}
@@ -1017,7 +1017,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return false;
}
synchronized (mVpns) {
- final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
+ final Vpn vpn = getVpn(UserHandle.getUserId(uid));
if (vpn != null && vpn.isBlockingUid(uid)) {
return true;
}
@@ -1094,7 +1094,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
final int user = UserHandle.getUserId(uid);
int vpnNetId = NETID_UNSET;
synchronized (mVpns) {
- final Vpn vpn = mVpns.get(user);
+ final Vpn vpn = getVpn(user);
if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();
}
NetworkAgentInfo nai;
@@ -1224,7 +1224,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (!mLockdownEnabled) {
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn != null) {
Network[] networks = vpn.getUnderlyingNetworks();
if (networks != null) {
@@ -1339,7 +1339,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ return isActiveNetworkMeteredCommon(Binder.getCallingUid());
+ }
+
+ @Override
+ public boolean isActiveNetworkMeteredForUid(int uid) {
+ enforceConnectivityInternalPermission();
+
+ return isActiveNetworkMeteredCommon(uid);
+ }
+
+ private boolean isActiveNetworkMeteredCommon(int uid) {
final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -3414,7 +3424,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled();
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn != null) {
return vpn.prepare(oldPackage, newPackage);
} else {
@@ -3441,7 +3451,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceCrossUserPermission(userId);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn != null) {
vpn.setPackageAuthorization(packageName, authorized);
}
@@ -3460,7 +3470,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
- return mVpns.get(user).establish(config);
+ return getVpn(user).establish(config);
}
}
@@ -3477,7 +3487,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
- mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+ getVpn(user).startLegacyVpn(profile, mKeyStore, egress);
}
}
@@ -3491,7 +3501,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceCrossUserPermission(userId);
synchronized (mVpns) {
- return mVpns.get(userId).getLegacyVpnInfo();
+ return getVpn(userId).getLegacyVpnInfo();
}
}
@@ -3555,7 +3565,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
public VpnConfig getVpnConfig(int userId) {
enforceCrossUserPermission(userId);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn != null) {
return vpn.getVpnConfig();
} else {
@@ -3589,7 +3599,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
- Vpn vpn = mVpns.get(user);
+ Vpn vpn = getVpn(user);
if (vpn == null) {
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
return false;
@@ -3636,7 +3646,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
private boolean startAlwaysOnVpn(int userId) {
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn == null) {
// Shouldn't happen as all codepaths that point here should have checked the Vpn
// exists already.
@@ -3654,7 +3664,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceCrossUserPermission(userId);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
@@ -3674,7 +3684,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
@@ -3696,7 +3706,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceCrossUserPermission(userId);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
+ Vpn vpn = getVpn(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return null;
@@ -3842,22 +3852,38 @@ public class ConnectivityService extends IConnectivityManager.Stub
private void onUserStart(int userId) {
synchronized (mVpns) {
- Vpn userVpn = mVpns.get(userId);
+ Vpn userVpn = getVpn(userId);
if (userVpn != null) {
loge("Starting user already has a VPN");
return;
}
userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
- mVpns.put(userId, userVpn);
+ setVpn(userId, userVpn);
}
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
}
}
+ /** @hide */
+ @VisibleForTesting
+ Vpn getVpn(int userId) {
+ synchronized (mVpns) {
+ return mVpns.get(userId);
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ void setVpn(int userId, Vpn userVpn) {
+ synchronized (mVpns) {
+ mVpns.put(userId, userVpn);
+ }
+ }
+
private void onUserStop(int userId) {
synchronized (mVpns) {
- Vpn userVpn = mVpns.get(userId);
+ Vpn userVpn = getVpn(userId);
if (userVpn == null) {
loge("Stopped user has no VPN");
return;
@@ -5429,7 +5455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
- return mVpns.get(user).addAddress(address, prefixLength);
+ return getVpn(user).addAddress(address, prefixLength);
}
}
@@ -5438,7 +5464,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
- return mVpns.get(user).removeAddress(address, prefixLength);
+ return getVpn(user).removeAddress(address, prefixLength);
}
}
@@ -5448,7 +5474,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
int user = UserHandle.getUserId(Binder.getCallingUid());
boolean success;
synchronized (mVpns) {
- success = mVpns.get(user).setUnderlyingNetworks(networks);
+ success = getVpn(user).setUnderlyingNetworks(networks);
}
if (success) {
notifyIfacesChangedForNetworkStats();
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 78367fe97a54..4d5a920dd54f 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -112,10 +112,8 @@ public final class ConnectivityController extends StateController implements
final boolean connected = (info != null) && info.isConnected();
final boolean connectionUsable = connected && validated;
- final boolean metered = connected && (capabilities != null)
- && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- final boolean unmetered = connected && (capabilities != null)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid);
+ final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid);
final boolean notRoaming = connected && (info != null)
&& !info.isRoaming();
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6674f20317b3..504a2dbc362b 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkCapabilities.*;
@@ -102,6 +103,7 @@ import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
+import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -333,6 +335,9 @@ public class ConnectivityServiceTest extends AndroidTestCase {
case TRANSPORT_WIFI_AWARE:
mScore = 20;
break;
+ case TRANSPORT_VPN:
+ mScore = 0;
+ break;
default:
throw new UnsupportedOperationException("unimplemented network type");
}
@@ -868,6 +873,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
return TYPE_WIFI;
case TRANSPORT_CELLULAR:
return TYPE_MOBILE;
+ case TRANSPORT_VPN:
+ return TYPE_VPN;
default:
return TYPE_NONE;
}
@@ -3447,4 +3454,84 @@ public class ConnectivityServiceTest extends AndroidTestCase {
return;
}
}
+
+ @SmallTest
+ public void testVpnNetworkMetered() {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(callback);
+
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ final TestNetworkCallback cellCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(cellRequest, cellCallback);
+
+ // Setup cellular
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+
+ // Verify meteredness of cellular
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Setup Wifi
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+
+ // Verify meteredness of WiFi
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Verify that setting unmetered on Wifi changes ActiveNetworkMetered
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Setup VPN
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ vpnNetworkAgent.connect(true);
+
+ Vpn mockVpn = mock(Vpn.class);
+ when(mockVpn.appliesToUid(anyInt())).thenReturn(true);
+ when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId);
+
+ Vpn oldVpn = mService.getVpn(UserHandle.myUserId());
+ mService.setVpn(UserHandle.myUserId(), mockVpn);
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Verify meteredness of VPN on default network
+ when(mockVpn.getUnderlyingNetworks()).thenReturn(null);
+ assertFalse(mCm.isActiveNetworkMetered());
+ assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));
+
+ // Verify meteredness of VPN on unmetered wifi
+ when(mockVpn.getUnderlyingNetworks())
+ .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()});
+ assertFalse(mCm.isActiveNetworkMetered());
+ assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));
+
+ // Set WiFi as metered, then check to see that it has been updated on the VPN
+ mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+ assertTrue(mCm.isActiveNetworkMetered());
+ assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid()));
+
+ // Switch to cellular
+ when(mockVpn.getUnderlyingNetworks())
+ .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()});
+ assertTrue(mCm.isActiveNetworkMetered());
+ assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid()));
+
+ // Test unmetered cellular
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
+ assertFalse(mCm.isActiveNetworkMetered());
+ assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid()));
+
+ mService.setVpn(UserHandle.myUserId(), oldVpn);
+ mCm.unregisterNetworkCallback(callback);
+ }
}