Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -27854,6 +27854,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[]); core/java/android/net/VpnService.java +21 −0 Original line number Diff line number Diff line Loading @@ -790,6 +790,27 @@ public class VpnService extends Service { return this; } /** * 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 Loading core/java/com/android/internal/net/VpnConfig.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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; Loading services/core/java/com/android/server/connectivity/Vpn.java +29 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -226,6 +227,7 @@ public class Vpn { mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserHandle); mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); try { netService.registerObserver(mObserver); Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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()) { Loading tests/net/java/com/android/server/ConnectivityServiceTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -906,6 +906,7 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); mConfig.isMetered = false; } @Override Loading Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -27854,6 +27854,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[]);
core/java/android/net/VpnService.java +21 −0 Original line number Diff line number Diff line Loading @@ -790,6 +790,27 @@ public class VpnService extends Service { return this; } /** * 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 Loading
core/java/com/android/internal/net/VpnConfig.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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; Loading
services/core/java/com/android/server/connectivity/Vpn.java +29 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -226,6 +227,7 @@ public class Vpn { mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserHandle); mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); try { netService.registerObserver(mObserver); Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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()) { Loading
tests/net/java/com/android/server/ConnectivityServiceTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -906,6 +906,7 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); mConfig.isMetered = false; } @Override Loading