diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java index c612271ee7..beb18219b7 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java @@ -26,6 +26,8 @@ import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0; +import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; @@ -280,6 +282,18 @@ public class OffloadController { } } + @Override + public void onWarningReached() { + if (!started()) return; + mLog.log("onWarningReached"); + + updateStatsForCurrentUpstream(); + if (mStatsProvider != null) { + mStatsProvider.pushTetherStats(); + mStatsProvider.notifyWarningReached(); + } + } + @Override public void onNatTimeoutUpdate(int proto, String srcAddr, int srcPort, @@ -417,7 +431,11 @@ public class OffloadController { @Override public void onSetAlert(long quotaBytes) { - // TODO: Ask offload HAL to notify alert without stopping traffic. + // Ignore set alert calls from HAL V1.1 since the hardware supports set warning now. + // Thus, the software polling mechanism is not needed. + if (!useStatsPolling()) { + return; + } // Post it to handler thread since it access remaining quota bytes. mHandler.post(() -> { updateAlertQuota(quotaBytes); @@ -502,12 +520,17 @@ public class OffloadController { private boolean isPollingStatsNeeded() { return started() && mRemainingAlertQuota > 0 + && useStatsPolling() && !TextUtils.isEmpty(currentUpstreamInterface()) && mDeps.getTetherConfig() != null && mDeps.getTetherConfig().getOffloadPollInterval() >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; } + private boolean useStatsPolling() { + return mControlHalVersion == OFFLOAD_HAL_VERSION_1_0; + } + private boolean maybeUpdateDataWarningAndLimit(String iface) { // setDataLimit or setDataWarningAndLimit may only be called while offload is occurring // on this upstream. @@ -516,7 +539,13 @@ public class OffloadController { } final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE); - return mHwInterface.setDataLimit(iface, quota.limitBytes); + final boolean ret; + if (mControlHalVersion >= OFFLOAD_HAL_VERSION_1_1) { + ret = mHwInterface.setDataWarningAndLimit(iface, quota.warningBytes, quota.limitBytes); + } else { + ret = mHwInterface.setDataLimit(iface, quota.limitBytes); + } + return ret; } private void updateStatsForCurrentUpstream() { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java index 14c34f02b4..d800816055 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java @@ -149,6 +149,7 @@ public class OffloadControllerTest { when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true); when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats()); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true); } private void enableOffload() { @@ -503,80 +504,166 @@ public class OffloadControllerTest { } /** - * Test OffloadController that uses V1.0 HAL setDataLimit with NetworkStatsProvider#onSetLimit - * which is called by R framework. + * Test OffloadController with different combinations of HAL and framework versions can set + * data warning and/or limit correctly. */ @Test - public void testSetDataLimit() throws Exception { + public void testSetDataWarningAndLimit() throws Exception { + // Verify the OffloadController is called by R framework, where the framework doesn't send + // warning. + checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_0); + checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_1); + // Verify the OffloadController is called by S+ framework, where the framework sends + // warning along with limit. + checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_0); + checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_1); + } + + private void checkSetDataWarningAndLimit(boolean isProviderSetWarning, int controlVersion) + throws Exception { enableOffload(); final OffloadController offload = - startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); + startOffloadController(controlVersion, true /*expectStart*/); final String ethernetIface = "eth1"; final String mobileIface = "rmnet_data0"; final long ethernetLimit = 12345; + final long mobileWarning = 123456; final long mobileLimit = 12345678; final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); final InOrder inOrder = inOrder(mHardware); - when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); + when(mHardware.setUpstreamParameters( + any(), any(), any(), any())).thenReturn(true); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true); + offload.setUpstreamLinkProperties(lp); + // Applying an interface sends the initial quota to the hardware. + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE, + Long.MAX_VALUE); + } else { + inOrder.verify(mHardware).setDataLimit(ethernetIface, Long.MAX_VALUE); + } + inOrder.verifyNoMoreInteractions(); + + // Verify that set to unlimited again won't cause duplicated calls to the hardware. + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(ethernetIface, + NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED); + } else { + mTetherStatsProvider.onSetLimit(ethernetIface, NetworkStatsProvider.QUOTA_UNLIMITED); + } + waitForIdle(); + inOrder.verifyNoMoreInteractions(); // Applying an interface quota to the current upstream immediately sends it to the hardware. - mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(ethernetIface, + NetworkStatsProvider.QUOTA_UNLIMITED, ethernetLimit); + } else { + mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit); + } waitForIdle(); - inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE, + ethernetLimit); + } else { + inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); + } inOrder.verifyNoMoreInteractions(); // Applying an interface quota to another upstream does not take any immediate action. - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); - inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(), + anyLong()); + } else { + 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); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, + isProviderSetWarning ? mobileWarning : Long.MAX_VALUE, + mobileLimit); + } else { + inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit); + } // Setting a limit of NetworkStatsProvider.QUOTA_UNLIMITED causes the limit to be set // to Long.MAX_VALUE. - mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, + NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED); + } waitForIdle(); - inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, Long.MAX_VALUE, + Long.MAX_VALUE); + } else { + inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); + } - // If setting upstream parameters fails, then the data limit is not set. + // If setting upstream parameters fails, then the data warning and limit is not set. when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false); lp.setInterfaceName(ethernetIface); offload.setUpstreamLinkProperties(lp); - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(), + anyLong()); - // If setting the data limit fails while changing upstreams, offload is stopped. + // If setting the data warning and/or limit fails while changing upstreams, offload is + // stopped. when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(false); lp.setInterfaceName(mobileIface); offload.setUpstreamLinkProperties(lp); - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); inOrder.verify(mHardware).getForwardedStats(ethernetIface); inOrder.verify(mHardware).stopOffloadControl(); } @Test - public void testDataLimitCallback() throws Exception { + public void testDataWarningAndLimitCallback() throws Exception { enableOffload(); - final OffloadController offload = - startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); + startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedLimitReached(); mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.expectNotifyWarningOrLimitReached(); + + startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/); + callback = mControlCallbackCaptor.getValue(); + callback.onWarningReached(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.expectNotifyWarningOrLimitReached(); } @Test @@ -764,9 +851,7 @@ public class OffloadControllerTest { // Initialize with fake eth upstream. final String ethernetIface = "eth1"; InOrder inOrder = inOrder(mHardware); - final LinkProperties lp = new LinkProperties(); - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); + offload.setUpstreamLinkProperties(makeEthernetLinkProperties()); // Previous upstream was null, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); @@ -799,4 +884,33 @@ public class OffloadControllerTest { mTetherStatsProviderCb.assertNoCallback(); verify(mHardware, never()).getForwardedStats(any()); } + + private static LinkProperties makeEthernetLinkProperties() { + final String ethernetIface = "eth1"; + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(ethernetIface); + return lp; + } + + private void checkSoftwarePollingUsed(int controlVersion) throws Exception { + enableOffload(); + setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + OffloadController offload = + startOffloadController(controlVersion, true /*expectStart*/); + offload.setUpstreamLinkProperties(makeEthernetLinkProperties()); + mTetherStatsProvider.onSetAlert(0); + waitForIdle(); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + mTetherStatsProviderCb.assertNoCallback(); + } else { + mTetherStatsProviderCb.expectNotifyAlertReached(); + } + verify(mHardware, never()).getForwardedStats(any()); + } + + @Test + public void testSoftwarePollingUsed() throws Exception { + checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_0); + checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_1); + } }