Loading packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java +88 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -115,11 +117,33 @@ public class OffloadController { // includes upstream interfaces that have a quota set. private HashMap<String, Long> 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; Loading @@ -135,6 +159,7 @@ public class OffloadController { provider = null; } mStatsProvider = provider; mDeps = deps; } /** Start hardware offload. */ Loading Loading @@ -240,6 +265,7 @@ public class OffloadController { mLog.log("tethering offload started"); mNatUpdateCallbacksReceived = 0; mNatUpdateNetlinkErrors = 0; maybeSchedulePollingStats(); } return isStarted; } Loading @@ -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"); } Loading Loading @@ -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(); }); } } Loading @@ -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())) { Loading Loading @@ -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 Loading packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +0 −1 Original line number Diff line number Diff line Loading @@ -308,7 +308,6 @@ public class OffloadHardwareInterface { return stats; } mLog.log(logmsg + YIELDS + stats); return stats; } Loading packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +1 −1 Original line number Diff line number Diff line Loading @@ -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<>(); Loading packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +7 −1 Original line number Diff line number Diff line Loading @@ -116,6 +116,12 @@ public class OffloadControllerTest { private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> 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); Loading Loading @@ -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<OffloadController.OffloadTetheringStatsProvider> tetherStatsProviderCaptor = ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class); Loading Loading
packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java +88 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -115,11 +117,33 @@ public class OffloadController { // includes upstream interfaces that have a quota set. private HashMap<String, Long> 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; Loading @@ -135,6 +159,7 @@ public class OffloadController { provider = null; } mStatsProvider = provider; mDeps = deps; } /** Start hardware offload. */ Loading Loading @@ -240,6 +265,7 @@ public class OffloadController { mLog.log("tethering offload started"); mNatUpdateCallbacksReceived = 0; mNatUpdateNetlinkErrors = 0; maybeSchedulePollingStats(); } return isStarted; } Loading @@ -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"); } Loading Loading @@ -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(); }); } } Loading @@ -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())) { Loading Loading @@ -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 Loading
packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +0 −1 Original line number Diff line number Diff line Loading @@ -308,7 +308,6 @@ public class OffloadHardwareInterface { return stats; } mLog.log(logmsg + YIELDS + stats); return stats; } Loading
packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +1 −1 Original line number Diff line number Diff line Loading @@ -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<>(); Loading
packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +7 −1 Original line number Diff line number Diff line Loading @@ -116,6 +116,12 @@ public class OffloadControllerTest { private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> 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); Loading Loading @@ -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<OffloadController.OffloadTetheringStatsProvider> tetherStatsProviderCaptor = ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class); Loading