Merge changes I82d3bee0,I9c9413d7 am: deb4eb5d05 am: 51c3d6a8bf

am: 0cf31d4d6d

Change-Id: I759ed9a79b6ebfa2ae6143167c0dd7d0ad84c67f
This commit is contained in:
Lorenzo Colitti
2017-08-15 02:25:36 +00:00
committed by android-build-merger
5 changed files with 193 additions and 28 deletions

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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,36 +182,38 @@ public class OffloadController {
@Override
public NetworkStats getTetherStats() {
NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
CountDownLatch latch = new CountDownLatch(1);
mHandler.post(() -> {
try {
NetworkStats.Entry entry = new NetworkStats.Entry();
entry.set = SET_DEFAULT;
entry.tag = TAG_NONE;
entry.uid = UID_TETHERING;
// 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;
entry.uid = UID_TETHERING;
updateStatsForCurrentUpstream();
updateStatsForCurrentUpstream();
for (String iface : mForwardedStats.keySet()) {
entry.iface = iface;
entry.rxBytes = mForwardedStats.get(iface).rxBytes;
entry.txBytes = mForwardedStats.get(iface).txBytes;
stats.addValues(entry);
}
} finally {
latch.countDown();
for (String iface : mForwardedStats.keySet()) {
entry.iface = iface;
entry.rxBytes = mForwardedStats.get(iface).rxBytes;
entry.txBytes = mForwardedStats.get(iface).txBytes;
stats.addValues(entry);
}
});
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");
}
}, 0);
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);
});
}
}
private void maybeUpdateStats(String 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() {

View File

@@ -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;

View File

@@ -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;
@@ -110,6 +112,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"));
@@ -417,4 +425,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();
}
}