Merge changes I38628dad,I2467b647 into rvc-dev

* changes:
  [SP18.1] add dependency object to OffloadController
  [SP18] Poll network stats in OffloadController to support data warning
This commit is contained in:
Junyu Lai
2020-04-30 06:07:25 +00:00
committed by Android (Google) Code Review
4 changed files with 96 additions and 4 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,11 +117,33 @@ 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;
@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, public OffloadController(Handler h, OffloadHardwareInterface hwi,
ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) { ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log,
@NonNull Dependencies deps) {
mHandler = h; mHandler = h;
mHwInterface = hwi; mHwInterface = hwi;
mContentResolver = contentResolver; mContentResolver = contentResolver;
@@ -135,6 +159,7 @@ public class OffloadController {
provider = null; provider = null;
} }
mStatsProvider = provider; mStatsProvider = provider;
mDeps = deps;
} }
/** Start hardware offload. */ /** Start hardware offload. */
@@ -240,6 +265,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 +281,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 +374,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 +400,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, mDeps.getPerformPollInterval());
}
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 +499,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;
} }

View File

@@ -273,7 +273,7 @@ public class Tethering {
mHandler = mTetherMasterSM.getHandler(); mHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(mHandler, mOffloadController = new OffloadController(mHandler,
mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
statsManager, mLog); statsManager, mLog, new OffloadController.Dependencies());
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK); TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new LinkedHashSet<>(); mForwardedDownstreams = new LinkedHashSet<>();

View File

@@ -116,6 +116,12 @@ public class OffloadControllerTest {
private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor = private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class); ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
private MockContentResolver mContentResolver; private MockContentResolver mContentResolver;
private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() {
@Override
int getPerformPollInterval() {
return 0;
}
};
@Before public void setUp() { @Before public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
@@ -150,7 +156,7 @@ public class OffloadControllerTest {
private OffloadController makeOffloadController() throws Exception { private OffloadController makeOffloadController() throws Exception {
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), 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> final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
tetherStatsProviderCaptor = tetherStatsProviderCaptor =
ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class); ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);