diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index ee6b9f12f9..b520bc8808 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -134,5 +134,5 @@ java_library { java_library { name: "framework-tethering-stubs-module_libs_api", srcs: [":framework-tethering-stubs-srcs-module_libs_api"], - defaults: ["framework-module-stubs-lib-defaults-systemapi"], + defaults: ["framework-module-stubs-lib-defaults-module_libs_api"], } diff --git a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java index 82a26beada..4f8ad8a221 100644 --- a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -169,7 +169,7 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { *

If not set, the default value is null. */ public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) { - this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr); + this.singleClientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr); return this; } diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java index c007c174fe..1817f35f1d 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java @@ -23,6 +23,7 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; 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 android.annotation.NonNull; @@ -76,6 +77,7 @@ public class OffloadController { private static final boolean DBG = false; private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); + private static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; @VisibleForTesting enum StatsType { @@ -115,11 +117,33 @@ public class OffloadController { // includes upstream interfaces that have a quota set. private HashMap mInterfaceQuotas = new HashMap<>(); + // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert + // quota is interface independent and global for tether offload. Note that this is only + // accessed on the handler thread and in the constructor. + private long mRemainingAlertQuota = QUOTA_UNLIMITED; + // Runnable that used to schedule the next stats poll. + private final Runnable mScheduledPollingTask = () -> { + updateStatsForCurrentUpstream(); + maybeSchedulePollingStats(); + }; + private int mNatUpdateCallbacksReceived; private int mNatUpdateNetlinkErrors; + @NonNull + private final Dependencies mDeps; + + // TODO: Put more parameters in constructor into dependency object. + static class Dependencies { + int getPerformPollInterval() { + // TODO: Consider make this configurable. + return DEFAULT_PERFORM_POLL_INTERVAL_MS; + } + } + public OffloadController(Handler h, OffloadHardwareInterface hwi, - ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) { + ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log, + @NonNull Dependencies deps) { mHandler = h; mHwInterface = hwi; mContentResolver = contentResolver; @@ -135,6 +159,7 @@ public class OffloadController { provider = null; } mStatsProvider = provider; + mDeps = deps; } /** Start hardware offload. */ @@ -240,6 +265,7 @@ public class OffloadController { mLog.log("tethering offload started"); mNatUpdateCallbacksReceived = 0; mNatUpdateNetlinkErrors = 0; + maybeSchedulePollingStats(); } return isStarted; } @@ -255,6 +281,9 @@ public class OffloadController { mHwInterface.stopOffloadControl(); mControlInitialized = false; mConfigInitialized = false; + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } if (wasStarted) mLog.log("tethering offload stopped"); } @@ -345,6 +374,11 @@ public class OffloadController { @Override public void onSetAlert(long quotaBytes) { // TODO: Ask offload HAL to notify alert without stopping traffic. + // Post it to handler thread since it access remaining quota bytes. + mHandler.post(() -> { + updateAlertQuota(quotaBytes); + maybeSchedulePollingStats(); + }); } } @@ -366,15 +400,66 @@ public class OffloadController { // the stats for each interface, and does not observe partial writes where rxBytes is // updated and txBytes is not. ForwardedStats diff = mHwInterface.getForwardedStats(iface); + final long usedAlertQuota = diff.rxBytes + diff.txBytes; ForwardedStats base = mForwardedStats.get(iface); if (base != null) { diff.add(base); } + + // Update remaining alert quota if it is still positive. + if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) { + // Trim to zero if overshoot. + final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0); + updateAlertQuota(newQuota); + } + mForwardedStats.put(iface, diff); // diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from // mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately. } + /** + * Update remaining alert quota, fire the {@link NetworkStatsProvider#notifyAlertReached()} + * callback when it reaches zero. This can be invoked either from service setting the alert, or + * {@code maybeUpdateStats} when updating stats. Note that this can be only called on + * handler thread. + * + * @param newQuota non-negative value to indicate the new quota, or + * {@link NetworkStatsProvider#QUOTA_UNLIMITED} to indicate there is no + * quota. + */ + private void updateAlertQuota(long newQuota) { + if (newQuota < QUOTA_UNLIMITED) { + throw new IllegalArgumentException("invalid quota value " + newQuota); + } + if (mRemainingAlertQuota == newQuota) return; + + mRemainingAlertQuota = newQuota; + if (mRemainingAlertQuota == 0) { + mLog.i("notifyAlertReached"); + if (mStatsProvider != null) mStatsProvider.notifyAlertReached(); + } + } + + /** + * Schedule polling if needed, this will be stopped if offload has been + * stopped or remaining quota reaches zero or upstream is empty. + * Note that this can be only called on handler thread. + */ + private void maybeSchedulePollingStats() { + if (!isPollingStatsNeeded()) return; + + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } + mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); + } + + private boolean isPollingStatsNeeded() { + return started() && mRemainingAlertQuota > 0 + && !TextUtils.isEmpty(currentUpstreamInterface()); + } + private boolean maybeUpdateDataLimit(String iface) { // setDataLimit may only be called while offload is occurring on this upstream. if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) { @@ -414,6 +499,8 @@ public class OffloadController { final String iface = currentUpstreamInterface(); if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); + maybeSchedulePollingStats(); + // 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 diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index c4a1078d0b..293f8eae32 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -308,7 +308,6 @@ public class OffloadHardwareInterface { return stats; } - mLog.log(logmsg + YIELDS + stats); return stats; } diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 4e16c49caa..0a95a5e007 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -273,7 +273,7 @@ public class Tethering { mHandler = mTetherMasterSM.getHandler(); mOffloadController = new OffloadController(mHandler, mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), - statsManager, mLog); + statsManager, mLog, new OffloadController.Dependencies()); mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); diff --git a/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java index f8eb1476ba..a8857b2e5c 100644 --- a/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java +++ b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java @@ -110,7 +110,7 @@ public class DhcpServingParamsParcelExtTest { @Test public void testSetClientAddr() { mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS); - assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr); + assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.singleClientAddr); } private static Inet4Address inet4Addr(String addr) { 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 65797200fa..088a663190 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java @@ -116,6 +116,12 @@ public class OffloadControllerTest { private final ArgumentCaptor mControlCallbackCaptor = ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class); private MockContentResolver mContentResolver; + private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() { + @Override + int getPerformPollInterval() { + return 0; + } + }; @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -150,7 +156,7 @@ public class OffloadControllerTest { private OffloadController makeOffloadController() throws Exception { OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), - mHardware, mContentResolver, mStatsManager, new SharedLog("test")); + mHardware, mContentResolver, mStatsManager, new SharedLog("test"), mDeps); final ArgumentCaptor tetherStatsProviderCaptor = ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 0c86eeb6cd..0363f5f998 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -1689,7 +1689,7 @@ public class TetheringTest { final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue(); assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress()); assertEquals(24, params.serverAddrPrefixLength); - assertEquals(clientAddrParceled, params.clientAddr); + assertEquals(clientAddrParceled, params.singleClientAddr); } @Test