Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62a9b66a authored by Varun Anand's avatar Varun Anand Committed by Gerrit Code Review
Browse files

Merge "Add an API that allows VPNs to declare themselves as metered."

parents 8c43f731 1215f09b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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[]);
+21 −0
Original line number Diff line number Diff line
@@ -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
+3 −0
Original line number Diff line number Diff line
@@ -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;
+29 −3
Original line number Diff line number Diff line
@@ -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()) {
+1 −0
Original line number Diff line number Diff line
@@ -906,6 +906,7 @@ public class ConnectivityServiceTest {
            mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
            mConnected = true;
            mConfig = new VpnConfig();
            mConfig.isMetered = false;
        }

        @Override
Loading