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

Commit 0cf31d4d authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by android-build-merger
Browse files

Merge changes I82d3bee0,I9c9413d7 am: deb4eb5d

am: 51c3d6a8

Change-Id: Ie7e8806faecdad96b4033404709fb3aebc4bdd0f
parents c04cc3bb 51c3d6a8
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.net;
import android.net.NetworkStats;

/**
 * Interface that allows NetworkManagementService to query for tethering statistics.
 * Interface for NetworkManagementService to query tethering statistics and set data limits.
 *
 * TODO: this does not really need to be an interface since Tethering runs in the same process
 * as NetworkManagementService. Consider refactoring Tethering to use direct access to
@@ -29,5 +29,14 @@ import android.net.NetworkStats;
 * @hide
 */
interface ITetheringStatsProvider {
    // Returns cumulative statistics for all tethering sessions since boot, on all upstreams.
    NetworkStats getTetherStats();

    // Sets the interface quota for the specified upstream interface. This is defined as the number
    // of bytes, starting from zero and counting from now, after which data should stop being
    // forwarded to/from the specified upstream. A value of QUOTA_UNLIMITED means there is no limit.
    void setInterfaceQuota(String iface, long quotaBytes);

    // Indicates that no data usage limit is set.
    const int QUOTA_UNLIMITED = -1;
}
+27 −0
Original line number Diff line number Diff line
@@ -1567,6 +1567,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
            } catch (NativeDaemonConnectorException e) {
                throw e.rethrowAsParcelableException();
            }

            synchronized (mTetheringStatsProviders) {
                for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
                    try {
                        provider.setInterfaceQuota(iface, quotaBytes);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Problem setting tethering data limit on provider " +
                                mTetheringStatsProviders.get(provider) + ": " + e);
                    }
                }
            }
        }
    }

@@ -1593,6 +1604,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
            } catch (NativeDaemonConnectorException e) {
                throw e.rethrowAsParcelableException();
            }

            synchronized (mTetheringStatsProviders) {
                for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
                    try {
                        provider.setInterfaceQuota(iface, ITetheringStatsProvider.QUOTA_UNLIMITED);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Problem removing tethering data limit on provider " +
                                mTetheringStatsProviders.get(provider) + ": " + e);
                    }
                }
            }
        }
    }

@@ -1864,6 +1886,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
            }
            return stats;
        }

        @Override
        public void setInterfaceQuota(String iface, long quotaBytes) {
            // Do nothing. netd is already informed of quota changes in setInterfaceQuota.
        }
    }

    @Override
+65 −29
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
@@ -58,8 +57,6 @@ import java.util.concurrent.TimeUnit;
public class OffloadController {
    private static final String TAG = OffloadController.class.getSimpleName();

    private static final int STATS_FETCH_TIMEOUT_MS = 1000;

    private final Handler mHandler;
    private final OffloadHardwareInterface mHwInterface;
    private final ContentResolver mContentResolver;
@@ -76,9 +73,17 @@ public class OffloadController {
    private Set<String> mLastLocalPrefixStrs;

    // Maps upstream interface names to offloaded traffic statistics.
    // Always contains the latest value received from the hardware for each interface, regardless of
    // whether offload is currently running on that interface.
    private HashMap<String, OffloadHardwareInterface.ForwardedStats>
            mForwardedStats = new HashMap<>();

    // Maps upstream interface names to interface quotas.
    // Always contains the latest value received from the framework for each interface, regardless
    // of whether offload is currently running (or is even supported) on that interface. Only
    // includes upstream interfaces that have a quota set.
    private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();

    public OffloadController(Handler h, OffloadHardwareInterface hwi,
            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
        mHandler = h;
@@ -177,10 +182,10 @@ public class OffloadController {
        @Override
        public NetworkStats getTetherStats() {
            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
            CountDownLatch latch = new CountDownLatch(1);

            mHandler.post(() -> {
                try {
            // We can't just post to mHandler because we are mostly (but not always) called by
            // NetworkStatsService#performPollLocked, which is (currently) on the same thread as us.
            mHandler.runWithScissors(() -> {
                NetworkStats.Entry entry = new NetworkStats.Entry();
                entry.set = SET_DEFAULT;
                entry.tag = TAG_NONE;
@@ -194,18 +199,20 @@ public class OffloadController {
                    entry.txBytes = mForwardedStats.get(iface).txBytes;
                    stats.addValues(entry);
                }
                } finally {
                    latch.countDown();
                }
            });
            }, 0);

            try {
                latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms");
            return stats;
        }

            return stats;
        public void setInterfaceQuota(String iface, long quotaBytes) {
            mHandler.post(() -> {
                if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
                    mInterfaceQuotas.remove(iface);
                } else {
                    mInterfaceQuotas.put(iface, quotaBytes);
                }
                maybeUpdateDataLimit(iface);
            });
        }
    }

@@ -220,6 +227,22 @@ public class OffloadController {
        mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
    }

    private boolean maybeUpdateDataLimit(String iface) {
        // setDataLimit may only be called while offload is occuring on this upstream.
        if (!started() ||
                mUpstreamLinkProperties == null ||
                !TextUtils.equals(iface, mUpstreamLinkProperties.getInterfaceName())) {
            return true;
        }

        Long limit = mInterfaceQuotas.get(iface);
        if (limit == null) {
            limit = Long.MAX_VALUE;
        }

        return mHwInterface.setDataLimit(iface, limit);
    }

    private void updateStatsForCurrentUpstream() {
        if (mUpstreamLinkProperties != null) {
            maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
@@ -309,8 +332,21 @@ public class OffloadController {
            }
        }

        return mHwInterface.setUpstreamParameters(
        boolean success = mHwInterface.setUpstreamParameters(
                iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways));

        if (!success) {
           return success;
        }

        // Data limits can only be set once offload is running on the upstream.
        success = maybeUpdateDataLimit(iface);
        if (!success) {
            mLog.log("Setting data limit for " + iface + " failed, disabling offload.");
            stop();
        }

        return success;
    }

    private boolean computeAndPushLocalPrefixes() {
+21 −0
Original line number Diff line number Diff line
@@ -188,6 +188,27 @@ public class OffloadHardwareInterface {
        return results.success;
    }

    public boolean setDataLimit(String iface, long limit) {

        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);

        final CbResults results = new CbResults();
        try {
            mOffloadControl.setDataLimit(
                    iface, limit,
                    (boolean success, String errMsg) -> {
                        results.success = success;
                        results.errMsg = errMsg;
                    });
        } catch (RemoteException e) {
            record(logmsg, e);
            return false;
        }

        record(logmsg, results);
        return results.success;
    }

    public boolean setUpstreamParameters(
            String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
        iface = (iface != null) ? iface : NO_INTERFACE_NAME;
+72 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -45,6 +46,7 @@ import android.net.LinkProperties;
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.INetworkManagementService;
@@ -112,6 +114,12 @@ public class OffloadControllerTest {
        Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
    }

    private void waitForIdle() {
        ConditionVariable cv = new ConditionVariable();
        new Handler(Looper.getMainLooper()).post(() -> { cv.open(); });
        cv.block();
    }

    private OffloadController makeOffloadController() throws Exception {
        OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
                mHardware, mContentResolver, mNMService, new SharedLog("test"));
@@ -421,4 +429,68 @@ public class OffloadControllerTest {
        entry = stats.getValues(ethernetPosition, entry);
        assertNetworkStats(ethernetIface, ethernetStats, entry);
    }

    @Test
    public void testSetInterfaceQuota() throws Exception {
        setupFunctioningHardwareInterface();
        enableOffload();

        final OffloadController offload = makeOffloadController();
        offload.start();

        final String ethernetIface = "eth1";
        final String mobileIface = "rmnet_data0";
        final long ethernetLimit = 12345;
        final long mobileLimit = 12345678;

        final LinkProperties lp = new LinkProperties();
        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);

        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
        final InOrder inOrder = inOrder(mHardware);
        when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
        when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);

        // Applying an interface quota to the current upstream immediately sends it to the hardware.
        provider.setInterfaceQuota(ethernetIface, ethernetLimit);
        waitForIdle();
        inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
        inOrder.verifyNoMoreInteractions();

        // Applying an interface quota to another upstream does not take any immediate action.
        provider.setInterfaceQuota(mobileIface, mobileLimit);
        waitForIdle();
        inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());

        // Switching to that upstream causes the quota to be applied if the parameters were applied
        // correctly.
        lp.setInterfaceName(mobileIface);
        offload.setUpstreamLinkProperties(lp);
        waitForIdle();
        inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit);

        // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
        // to Long.MAX_VALUE.
        provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
        waitForIdle();
        inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);

        // If setting upstream parameters fails, then the data limit is not set.
        when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);
        provider.setInterfaceQuota(mobileIface, mobileLimit);
        waitForIdle();
        inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());

        // If setting the data limit fails while changing upstreams, offload is stopped.
        when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
        when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
        lp.setInterfaceName(mobileIface);
        offload.setUpstreamLinkProperties(lp);
        provider.setInterfaceQuota(mobileIface, mobileLimit);
        waitForIdle();
        inOrder.verify(mHardware).stopOffloadControl();
    }
}