Merge "[SP18] Poll network stats in OffloadController to support data warning" am: d25d88199f am: 73cfbe504e am: a10b3a3a89 am: 72d37e7460
Change-Id: I91293aa698fe29da0b915db93336e4c18d363735
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index c007c17..445a09d 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.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 @@
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,6 +117,16 @@
// 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;
@@ -240,6 +252,7 @@
mLog.log("tethering offload started");
mNatUpdateCallbacksReceived = 0;
mNatUpdateNetlinkErrors = 0;
+ maybeSchedulePollingStats();
}
return isStarted;
}
@@ -255,6 +268,9 @@
mHwInterface.stopOffloadControl();
mControlInitialized = false;
mConfigInitialized = false;
+ if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+ mHandler.removeCallbacks(mScheduledPollingTask);
+ }
if (wasStarted) mLog.log("tethering offload stopped");
}
@@ -345,6 +361,11 @@
@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 +387,66 @@
// 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, DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ }
+
+ 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 +486,8 @@
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 c4a1078..293f8ea 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -308,7 +308,6 @@
return stats;
}
- mLog.log(logmsg + YIELDS + stats);
return stats;
}