[SP18] Poll network stats in OffloadController to support data warning

The OEM implemented tether offload does not support
data warning since the HAL only tells the hardware about data limit
but not warning. However, to add such interface in HAL needs OEM to
comply and implement in hardware.

Thus, as a short-term solution, polls network statistics from HAL
and notify upper layer when it reaches the alert quota set by
NetworkStatsService.

Note that when CPU is sleeping, the data warning of tethering offload
will not work since the polling is also suspended.

Test: manual
Test: atest OffloadControllerTest
Bug: 149467454
Change-Id: I2467b64779b74cd5fec73b42fb303584f52cb1cb
Merged-In: I2467b64779b74cd5fec73b42fb303584f52cb1cb
(cherry picked from commit 93660e382c)
This commit is contained in:
Junyu Lai
2020-04-29 12:25:33 +00:00
parent 9258a08525
commit bf6fa99b2a
2 changed files with 74 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING; 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 android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
import android.annotation.NonNull; import android.annotation.NonNull;
@@ -76,6 +77,7 @@ public class OffloadController {
private static final boolean DBG = false; private static final boolean DBG = false;
private static final String ANYIP = "0.0.0.0"; private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
private static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000;
@VisibleForTesting @VisibleForTesting
enum StatsType { enum StatsType {
@@ -115,6 +117,16 @@ public class OffloadController {
// includes upstream interfaces that have a quota set. // includes upstream interfaces that have a quota set.
private HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); 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 mNatUpdateCallbacksReceived;
private int mNatUpdateNetlinkErrors; private int mNatUpdateNetlinkErrors;
@@ -240,6 +252,7 @@ public class OffloadController {
mLog.log("tethering offload started"); mLog.log("tethering offload started");
mNatUpdateCallbacksReceived = 0; mNatUpdateCallbacksReceived = 0;
mNatUpdateNetlinkErrors = 0; mNatUpdateNetlinkErrors = 0;
maybeSchedulePollingStats();
} }
return isStarted; return isStarted;
} }
@@ -255,6 +268,9 @@ public class OffloadController {
mHwInterface.stopOffloadControl(); mHwInterface.stopOffloadControl();
mControlInitialized = false; mControlInitialized = false;
mConfigInitialized = false; mConfigInitialized = false;
if (mHandler.hasCallbacks(mScheduledPollingTask)) {
mHandler.removeCallbacks(mScheduledPollingTask);
}
if (wasStarted) mLog.log("tethering offload stopped"); if (wasStarted) mLog.log("tethering offload stopped");
} }
@@ -345,6 +361,11 @@ public class OffloadController {
@Override @Override
public void onSetAlert(long quotaBytes) { public void onSetAlert(long quotaBytes) {
// TODO: Ask offload HAL to notify alert without stopping traffic. // 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 @@ public class OffloadController {
// the stats for each interface, and does not observe partial writes where rxBytes is // the stats for each interface, and does not observe partial writes where rxBytes is
// updated and txBytes is not. // updated and txBytes is not.
ForwardedStats diff = mHwInterface.getForwardedStats(iface); ForwardedStats diff = mHwInterface.getForwardedStats(iface);
final long usedAlertQuota = diff.rxBytes + diff.txBytes;
ForwardedStats base = mForwardedStats.get(iface); ForwardedStats base = mForwardedStats.get(iface);
if (base != null) { if (base != null) {
diff.add(base); 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); mForwardedStats.put(iface, diff);
// diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from // 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. // 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) { private boolean maybeUpdateDataLimit(String iface) {
// setDataLimit may only be called while offload is occurring on this upstream. // setDataLimit may only be called while offload is occurring on this upstream.
if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) { if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
@@ -414,6 +486,8 @@ public class OffloadController {
final String iface = currentUpstreamInterface(); final String iface = currentUpstreamInterface();
if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS);
maybeSchedulePollingStats();
// TODO: examine return code and decide what to do if programming // TODO: examine return code and decide what to do if programming
// upstream parameters fails (probably just wait for a subsequent // upstream parameters fails (probably just wait for a subsequent
// onOffloadEvent() callback to tell us offload is available again and // onOffloadEvent() callback to tell us offload is available again and

View File

@@ -308,7 +308,6 @@ public class OffloadHardwareInterface {
return stats; return stats;
} }
mLog.log(logmsg + YIELDS + stats);
return stats; return stats;
} }