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

Commit 9563c15d authored by Varun Anand's avatar Varun Anand Committed by android-build-merger
Browse files

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

am: 62a9b66a

Change-Id: Iadf012754e2e39f4b99efc97d5666dd0bc1ff2d6
parents 1dbfcbc6 62a9b66a
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