diff options
8 files changed, 311 insertions, 27 deletions
diff --git a/Android.mk b/Android.mk index aec1bde15439..700cdc15e728 100644 --- a/Android.mk +++ b/Android.mk @@ -221,6 +221,7 @@ LOCAL_SRC_FILES += \ core/java/android/net/INetworkScoreService.aidl \ core/java/android/net/INetworkStatsService.aidl \ core/java/android/net/INetworkStatsSession.aidl \ + core/java/android/net/ITetheringStatsProvider.aidl \ core/java/android/net/nsd/INsdManager.aidl \ core/java/android/nfc/IAppCallback.aidl \ core/java/android/nfc/INfcAdapter.aidl \ diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl new file mode 100644 index 000000000000..769086da42b4 --- /dev/null +++ b/core/java/android/net/ITetheringStatsProvider.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.NetworkStats; + +/** + * Interface that allows NetworkManagementService to query for tethering statistics. + * + * 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 + * NetworkManagementService instead of using INetworkManagementService, and then deleting this + * interface. + * + * @hide + */ +interface ITetheringStatsProvider { + NetworkStats getTetherStats(); +} diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 92e78bc8d977..3de217494ac5 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -20,6 +20,7 @@ package android.os; import android.net.InterfaceConfiguration; import android.net.INetd; import android.net.INetworkManagementEventObserver; +import android.net.ITetheringStatsProvider; import android.net.Network; import android.net.NetworkStats; import android.net.RouteInfo; @@ -207,6 +208,18 @@ interface INetworkManagementService void disableNat(String internalInterface, String externalInterface); /** + * Registers a {@code ITetheringStatsProvider} to provide tethering statistics. + * All registered providers will be called in order, and their results will be added together. + * Netd is always registered as a tethering stats provider. + */ + void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name); + + /** + * Unregisters a previously-registered {@code ITetheringStatsProvider}. + */ + void unregisterTetheringStatsProvider(ITetheringStatsProvider provider); + + /** ** PPPD **/ diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 15932cc2ee0c..aaec642ab4e2 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -18,6 +18,7 @@ package com.android.server; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.SHUTDOWN; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; @@ -53,6 +54,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.INetd; import android.net.INetworkManagementEventObserver; +import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; @@ -220,6 +222,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); + @GuardedBy("mTetheringStatsProviders") + private final HashMap<ITetheringStatsProvider, String> + mTetheringStatsProviders = Maps.newHashMap(); + private final Object mQuotaLock = new Object(); /** Set of interfaces with active quotas. */ @@ -319,6 +325,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); + + synchronized (mTetheringStatsProviders) { + mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd"); + } } static NetworkManagementService create(Context context, String socket) @@ -499,6 +509,23 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + @Override + public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + Preconditions.checkNotNull(provider); + synchronized(mTetheringStatsProviders) { + mTetheringStatsProviders.put(provider, name); + } + } + + @Override + public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + synchronized(mTetheringStatsProviders) { + mTetheringStatsProviders.remove(provider); + } + } + // Sync the state of the given chain with the native daemon. private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) { int size = uidFirewallRules.size(); @@ -1748,14 +1775,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } - @Override - public NetworkStats getNetworkStatsTethering() { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); - try { - final NativeDaemonEvent[] events = mConnector.executeForList( - "bandwidth", "gettetherstats"); + private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub { + @Override + public NetworkStats getTetherStats() { + final NativeDaemonEvent[] events; + try { + events = mConnector.executeForList("bandwidth", "gettetherstats"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); for (NativeDaemonEvent event : events) { if (event.getCode() != TetheringStatsListResult) continue; @@ -1781,8 +1810,24 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("problem parsing tethering stats: " + event); } } - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + return stats; + } + } + + @Override + public NetworkStats getNetworkStatsTethering() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); + synchronized (mTetheringStatsProviders) { + for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) { + try { + stats.combineAllValues(provider.getTetherStats()); + } catch (RemoteException e) { + Log.e(TAG, "Problem reading tethering stats from " + + mTetheringStatsProviders.get(provider) + ": " + e); + } + } } return stats; } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 3a4e07e96ad7..5ea6636db861 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -211,7 +211,7 @@ public class Tethering extends BaseNetworkObserver { final Handler smHandler = mTetherMasterSM.getHandler(); mOffloadController = new OffloadController(smHandler, deps.getOffloadHardwareInterface(smHandler, mLog), - mContext.getContentResolver(), + mContext.getContentResolver(), mNMService, mLog); mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); @@ -1755,6 +1755,11 @@ public class Tethering extends BaseNetworkObserver { pw.decreaseIndent(); } + pw.println("Hardware offload:"); + pw.increaseIndent(); + mOffloadController.dump(pw); + pw.decreaseIndent(); + pw.println("Log:"); pw.increaseIndent(); if (argsContain(args, SHORT_ARG)) { diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index b47386705a36..1a5ff778010c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -16,24 +16,38 @@ package com.android.server.connectivity.tethering; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import android.content.ContentResolver; +import android.net.ITetheringStatsProvider; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkStats; import android.net.RouteInfo; import android.net.util.SharedLog; import android.os.Handler; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.SystemClock; import android.provider.Settings; +import android.text.TextUtils; + +import com.android.internal.util.IndentingPrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; +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; /** * A class to encapsulate the business logic of programming the tethering @@ -44,6 +58,8 @@ import java.util.Set; 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; @@ -59,14 +75,25 @@ public class OffloadController { // prefixes representing only the locally-assigned IP addresses. private Set<String> mLastLocalPrefixStrs; + // Maps upstream interface names to offloaded traffic statistics. + private HashMap<String, OffloadHardwareInterface.ForwardedStats> + mForwardedStats = new HashMap<>(); + public OffloadController(Handler h, OffloadHardwareInterface hwi, - ContentResolver contentResolver, SharedLog log) { + ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) { mHandler = h; mHwInterface = hwi; mContentResolver = contentResolver; mLog = log.forSubComponent(TAG); mExemptPrefixes = new HashSet<>(); mLastLocalPrefixStrs = new HashSet<>(); + + try { + nms.registerTetheringStatsProvider( + new OffloadTetheringStatsProvider(), getClass().getSimpleName()); + } catch (RemoteException e) { + mLog.e("Cannot register offload stats provider: " + e); + } } public void start() { @@ -138,6 +165,7 @@ public class OffloadController { public void stop() { final boolean wasStarted = started(); + updateStatsForCurrentUpstream(); mUpstreamLinkProperties = null; mHwInterface.stopOffloadControl(); mControlInitialized = false; @@ -145,16 +173,76 @@ public class OffloadController { if (wasStarted) mLog.log("tethering offload stopped"); } + private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub { + @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; + + 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(); + } + }); + + 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; + } + } + + private void maybeUpdateStats(String iface) { + if (TextUtils.isEmpty(iface)) { + return; + } + + if (!mForwardedStats.containsKey(iface)) { + mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats()); + } + mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface)); + } + + private void updateStatsForCurrentUpstream() { + if (mUpstreamLinkProperties != null) { + maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName()); + } + } + public void setUpstreamLinkProperties(LinkProperties lp) { if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; + String prevUpstream = (mUpstreamLinkProperties != null) ? + mUpstreamLinkProperties.getInterfaceName() : null; + mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null; + // TODO: examine return code and decide what to do if programming // upstream parameters fails (probably just wait for a subsequent // onOffloadEvent() callback to tell us offload is available again and // then reapply all state). computeAndPushLocalPrefixes(); pushUpstreamParameters(); + + // Update stats after we've told the hardware to change routing so we don't miss packets. + maybeUpdateStats(prevUpstream); } public void setLocalPrefixes(Set<IpPrefix> localPrefixes) { @@ -262,4 +350,16 @@ public class OffloadController { for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString()); return localPrefixStrs; } + + public void dump(IndentingPrintWriter pw) { + if (isOffloadDisabled()) { + pw.println("Offload disabled"); + return; + } + pw.println("Offload HALs " + (started() ? "started" : "not started")); + LinkProperties lp = mUpstreamLinkProperties; + String upstream = (lp != null) ? lp.getInterfaceName() : null; + pw.println("Current upstream: " + upstream); + pw.println("Exempt prefixes: " + mLastLocalPrefixStrs); + } } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index 460d53e8f522..6f048e2ccd2f 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -178,6 +178,7 @@ public class TetheringTest { mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, mLooper.getLooper(), mSystemProperties, mTetheringDependencies); + verify(mNMService).registerTetheringStatsProvider(any(), anyString()); } @After diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java index 789ce6c3092a..45525e624d90 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -16,26 +16,38 @@ package com.android.server.connectivity.tethering; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; 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.anyObject; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.net.ITetheringStatsProvider; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkStats; import android.net.RouteInfo; import android.net.util.SharedLog; +import android.os.Handler; +import android.os.Looper; +import android.os.INetworkManagementService; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -66,11 +78,14 @@ public class OffloadControllerTest { @Mock private OffloadHardwareInterface mHardware; @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; + @Mock private INetworkManagementService mNMService; private final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); + private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor = + ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class); private MockContentResolver mContentResolver; - @Before public void setUp() throws Exception { + @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo); when(mContext.getPackageName()).thenReturn("OffloadControllerTest"); @@ -90,15 +105,24 @@ public class OffloadControllerTest { when(mHardware.initOffloadConfig()).thenReturn(true); when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class))) .thenReturn(true); + when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats()); } private void enableOffload() { Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); } + private OffloadController makeOffloadController() throws Exception { + OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), + mHardware, mContentResolver, mNMService, new SharedLog("test")); + verify(mNMService).registerTetheringStatsProvider( + mTetherStatsProviderCaptor.capture(), anyString()); + return offload; + } + // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available. // @Test - public void testNoSettingsValueDefaultDisabledDoesNotStart() { + public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception { setupFunctioningHardwareInterface(); when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1); try { @@ -106,8 +130,7 @@ public class OffloadControllerTest { fail(); } catch (SettingNotFoundException expected) {} - final OffloadController offload = - new OffloadController(null, mHardware, mContentResolver, new SharedLog("test")); + final OffloadController offload = makeOffloadController(); offload.start(); final InOrder inOrder = inOrder(mHardware); @@ -120,7 +143,7 @@ public class OffloadControllerTest { // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available. // @Test - public void testNoSettingsValueDefaultEnabledDoesStart() { + public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception { setupFunctioningHardwareInterface(); when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0); try { @@ -128,8 +151,7 @@ public class OffloadControllerTest { fail(); } catch (SettingNotFoundException expected) {} - final OffloadController offload = - new OffloadController(null, mHardware, mContentResolver, new SharedLog("test")); + final OffloadController offload = makeOffloadController(); offload.start(); final InOrder inOrder = inOrder(mHardware); @@ -141,12 +163,11 @@ public class OffloadControllerTest { } @Test - public void testSettingsAllowsStart() { + public void testSettingsAllowsStart() throws Exception { setupFunctioningHardwareInterface(); Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); - final OffloadController offload = - new OffloadController(null, mHardware, mContentResolver, new SharedLog("test")); + final OffloadController offload = makeOffloadController(); offload.start(); final InOrder inOrder = inOrder(mHardware); @@ -158,12 +179,11 @@ public class OffloadControllerTest { } @Test - public void testSettingsDisablesStart() { + public void testSettingsDisablesStart() throws Exception { setupFunctioningHardwareInterface(); Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1); - final OffloadController offload = - new OffloadController(null, mHardware, mContentResolver, new SharedLog("test")); + final OffloadController offload = makeOffloadController(); offload.start(); final InOrder inOrder = inOrder(mHardware); @@ -178,8 +198,7 @@ public class OffloadControllerTest { setupFunctioningHardwareInterface(); enableOffload(); - final OffloadController offload = - new OffloadController(null, mHardware, mContentResolver, new SharedLog("test")); + final OffloadController offload = makeOffloadController(); offload.start(); final InOrder inOrder = inOrder(mHardware); @@ -244,6 +263,7 @@ public class OffloadControllerTest { inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(testIfName), eq(ipv4Addr), eq(null), eq(null)); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); inOrder.verifyNoMoreInteractions(); final String ipv4Gateway = "192.0.2.1"; @@ -253,6 +273,7 @@ public class OffloadControllerTest { inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null)); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); inOrder.verifyNoMoreInteractions(); final String ipv6Gw1 = "fe80::cafe"; @@ -262,6 +283,7 @@ public class OffloadControllerTest { inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); ArrayList<String> v6gws = mStringArrayCaptor.getValue(); assertEquals(1, v6gws.size()); assertTrue(v6gws.contains(ipv6Gw1)); @@ -274,6 +296,7 @@ public class OffloadControllerTest { inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); v6gws = mStringArrayCaptor.getValue(); assertEquals(2, v6gws.size()); assertTrue(v6gws.contains(ipv6Gw1)); @@ -291,6 +314,7 @@ public class OffloadControllerTest { inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); v6gws = mStringArrayCaptor.getValue(); assertEquals(2, v6gws.size()); assertTrue(v6gws.contains(ipv6Gw1)); @@ -325,6 +349,7 @@ public class OffloadControllerTest { assertEquals(2, v6gws.size()); assertTrue(v6gws.contains(ipv6Gw1)); assertTrue(v6gws.contains(ipv6Gw2)); + inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); inOrder.verifyNoMoreInteractions(); // Completely identical LinkProperties updates are de-duped. @@ -335,4 +360,65 @@ public class OffloadControllerTest { anyObject(), anyObject(), anyObject(), anyObject()); inOrder.verifyNoMoreInteractions(); } + + private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) { + assertEquals(iface, entry.iface); + assertEquals(stats.rxBytes, entry.rxBytes); + assertEquals(stats.txBytes, entry.txBytes); + assertEquals(SET_DEFAULT, entry.set); + assertEquals(TAG_NONE, entry.tag); + assertEquals(UID_TETHERING, entry.uid); + } + + @Test + public void testGetForwardedStats() throws Exception { + setupFunctioningHardwareInterface(); + enableOffload(); + + final OffloadController offload = makeOffloadController(); + offload.start(); + + final String ethernetIface = "eth1"; + final String mobileIface = "rmnet_data0"; + + ForwardedStats ethernetStats = new ForwardedStats(); + ethernetStats.rxBytes = 12345; + ethernetStats.txBytes = 54321; + + ForwardedStats mobileStats = new ForwardedStats(); + mobileStats.rxBytes = 999; + mobileStats.txBytes = 99999; + + when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); + when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats); + + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(ethernetIface); + offload.setUpstreamLinkProperties(lp); + + lp.setInterfaceName(mobileIface); + offload.setUpstreamLinkProperties(lp); + + lp.setInterfaceName(ethernetIface); + offload.setUpstreamLinkProperties(lp); + + ethernetStats.rxBytes = 100000; + ethernetStats.txBytes = 100000; + offload.setUpstreamLinkProperties(null); + + NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats(); + assertEquals(2, stats.size()); + + NetworkStats.Entry entry = null; + int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1; + int mobilePosition = 1 - ethernetPosition; + + entry = stats.getValues(mobilePosition, entry); + assertNetworkStats(mobileIface, mobileStats, entry); + + ethernetStats.rxBytes = 12345 + 100000; + ethernetStats.txBytes = 54321 + 100000; + entry = stats.getValues(ethernetPosition, entry); + assertNetworkStats(ethernetIface, ethernetStats, entry); + } } |