diff options
| -rwxr-xr-x | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/net/VpnService.java | 21 | ||||
| -rw-r--r-- | core/java/com/android/internal/net/VpnConfig.java | 3 | ||||
| -rw-r--r-- | services/core/java/com/android/server/connectivity/Vpn.java | 32 | ||||
| -rw-r--r-- | tests/net/java/com/android/server/ConnectivityServiceTest.java | 1 | ||||
| -rw-r--r-- | tests/net/java/com/android/server/connectivity/VpnTest.java | 61 |
6 files changed, 99 insertions, 20 deletions
diff --git a/api/current.txt b/api/current.txt index bb400567fa6e..e248d71d4a19 100755 --- a/api/current.txt +++ b/api/current.txt @@ -27851,6 +27851,7 @@ package android.net { method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); + method public android.net.VpnService.Builder setMetered(boolean); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index dc099a46aa2a..784f23311103 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -791,6 +791,27 @@ public class VpnService extends Service { } /** + * Marks the VPN network as metered. A VPN network is classified as metered when the user is + * sensitive to heavy data usage due to monetary costs and/or data limitations. In such + * cases, you should set this to {@code true} so that apps on the system can avoid doing + * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN + * network to inherit its meteredness from its underlying networks. + * + * <p>VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be + * considered metered by default. + * + * @param isMetered {@code true} if VPN network should be treated as metered regardless of + * underlying network meteredness + * @return this {@link Builder} object to facilitate chaining method calls + * @see #setUnderlyingNetworks(Networks[]) + * @see ConnectivityManager#isActiveNetworkMetered() + */ + public Builder setMetered(boolean isMetered) { + mConfig.isMetered = isMetered; + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index da8605e645b4..65b974ba8b42 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -104,6 +104,7 @@ public class VpnConfig implements Parcelable { public boolean allowBypass; public boolean allowIPv4; public boolean allowIPv6; + public boolean isMetered = true; public Network[] underlyingNetworks; public ProxyInfo proxyInfo; @@ -165,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); + out.writeInt(isMetered ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); out.writeParcelable(proxyInfo, flags); } @@ -191,6 +193,7 @@ public class VpnConfig implements Parcelable { config.allowBypass = in.readInt() != 0; config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; + config.isMetered = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); config.proxyInfo = in.readParcelable(null); return config; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 250884431440..9141ccb387bd 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -165,6 +165,7 @@ public class Vpn { private final NetworkInfo mNetworkInfo; private String mPackage; private int mOwnerUID; + private boolean mIsPackageTargetingAtLeastQ; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -226,6 +227,7 @@ public class Vpn { mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); try { netService.registerObserver(mObserver); @@ -267,8 +269,11 @@ public class Vpn { public void updateCapabilities() { final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null; + // Only apps targeting Q and above can explicitly declare themselves as metered. + final boolean isAlwaysMetered = + mIsPackageTargetingAtLeastQ && (mConfig == null || mConfig.isMetered); updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks, - mNetworkCapabilities); + mNetworkCapabilities, isAlwaysMetered); if (mNetworkAgent != null) { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); @@ -277,11 +282,13 @@ public class Vpn { @VisibleForTesting public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, - NetworkCapabilities caps) { + NetworkCapabilities caps, boolean isAlwaysMetered) { int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN }; int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; - boolean metered = false; + // VPN's meteredness is OR'd with isAlwaysMetered and meteredness of its underlying + // networks. + boolean metered = isAlwaysMetered; boolean roaming = false; boolean congested = false; @@ -724,6 +731,7 @@ public class Vpn { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; mOwnerUID = getAppUid(newPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage); try { mNetd.allowProtect(mOwnerUID); } catch (Exception e) { @@ -789,6 +797,21 @@ public class Vpn { return result; } + private boolean doesPackageTargetAtLeastQ(String packageName) { + if (VpnConfig.LEGACY_VPN.equals(packageName)) { + return true; + } + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo appInfo = + pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle); + return appInfo.targetSdkVersion >= VERSION_CODES.Q; + } catch (NameNotFoundException unused) { + Log.w(TAG, "Can't find \"" + packageName + "\""); + return false; + } + } + public NetworkInfo getNetworkInfo() { return mNetworkInfo; } @@ -1076,6 +1099,8 @@ public class Vpn { // as rules are deleted. This prevents data leakage as the rules are moved over. agentDisconnect(oldNetworkAgent); } + // Set up VPN's capabilities such as meteredness. + updateCapabilities(); if (oldConnection != null) { mContext.unbindService(oldConnection); @@ -1776,6 +1801,7 @@ public class Vpn { config.user = profile.key; config.interfaze = iface; config.session = profile.name; + config.isMetered = false; config.addLegacyRoutes(profile.routes); if (!profile.dnsServers.isEmpty()) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 923c7dd5fb94..67fe8e559f3b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -903,6 +903,7 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); + mConfig.isMetered = false; } @Override diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 5b17224e41e5..46de3d0608ff 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -168,6 +168,8 @@ public class VpnTest { ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; when(mContext.getApplicationInfo()).thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); doNothing().when(mNetService).registerObserver(any()); } @@ -544,23 +546,28 @@ public class VpnTest { final Network wifi = new Network(2); final Map<Network, NetworkCapabilities> networks = new HashMap<>(); - networks.put(mobile, new NetworkCapabilities() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkDownstreamBandwidthKbps(10)); - networks.put(wifi, new NetworkCapabilities() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_ROAMING) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkUpstreamBandwidthKbps(20)); + networks.put( + mobile, + new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkDownstreamBandwidthKbps(10)); + networks.put( + wifi, + new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_NOT_ROAMING) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkUpstreamBandwidthKbps(20)); setMockedNetworks(networks); final NetworkCapabilities caps = new NetworkCapabilities(); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { }, caps); + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {}, caps, false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); @@ -570,17 +577,33 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile }, caps); + Vpn.updateCapabilities( + mConnectivityManager, + new Network[] {mobile}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); assertEquals(10, caps.getLinkDownstreamBandwidthKbps()); assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps()); - assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { wifi }, caps); + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(20, caps.getLinkUpstreamBandwidthKbps()); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); @@ -590,7 +613,11 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile, wifi }, caps); + Vpn.updateCapabilities( + mConnectivityManager, + new Network[] {mobile, wifi}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); |