Tell the system when tethering offload hits a limit.
Add a new tetherLimitReached method to INetworkManagementService,
and call it when the HAL notifies OffloadController because the
limit has been reached.
Bug: 29337859
Bug: 32163131
Test: builds
Test: OffloadControllerTest passes
(cherry picked from commit d66cf56ba662f10f2da1d0f844116632ad0a0dbb)
Change-Id: I89719fe7ec8bfd3c85d6cdca9c0d449aea86ef9d
Merged-In: I026e6aa9e7b371f316c0d97c3cf5e78abc1f5263
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 3de2174..316ef11 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -220,6 +220,21 @@
void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
/**
+ * Reports that a tethering provider has reached a data limit.
+ *
+ * Currently triggers a global alert, which causes NetworkStatsService to poll counters and
+ * re-evaluate data usage.
+ *
+ * This does not take an interface name because:
+ * 1. The tethering offload stats provider cannot reliably determine the interface on which the
+ * limit was reached, because the HAL does not provide it.
+ * 2. Firing an interface-specific alert instead of a global alert isn't really useful since in
+ * all cases of interest, the system responds to both in the same way - it polls stats, and
+ * then notifies NetworkPolicyManagerService of the fact.
+ */
+ void tetherLimitReached(ITetheringStatsProvider provider);
+
+ /**
** PPPD
**/
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 3d638be..51e2a70 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -529,6 +529,18 @@
}
}
+ @Override
+ public void tetherLimitReached(ITetheringStatsProvider provider) {
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+ synchronized(mTetheringStatsProviders) {
+ if (!mTetheringStatsProviders.containsKey(provider)) {
+ return;
+ }
+ // No current code examines the interface parameter in a global alert. Just pass null.
+ notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+ }
+ }
+
// Sync the state of the given chain with the native daemon.
private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
int size = uidFirewallRules.size();
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 55e290a..ad661d7 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -60,6 +60,8 @@
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
+ private final INetworkManagementService mNms;
+ private final ITetheringStatsProvider mStatsProvider;
private final SharedLog mLog;
private boolean mConfigInitialized;
private boolean mControlInitialized;
@@ -89,13 +91,14 @@
mHandler = h;
mHwInterface = hwi;
mContentResolver = contentResolver;
+ mNms = nms;
+ mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
try {
- nms.registerTetheringStatsProvider(
- new OffloadTetheringStatsProvider(), getClass().getSimpleName());
+ mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
} catch (RemoteException e) {
mLog.e("Cannot register offload stats provider: " + e);
}
@@ -150,7 +153,26 @@
@Override
public void onStoppedLimitReached() {
mLog.log("onStoppedLimitReached");
- // Poll for statistics and notify NetworkStats
+
+ // We cannot reliably determine on which interface the limit was reached,
+ // because the HAL interface does not specify it. We cannot just use the
+ // current upstream, because that might have changed since the time that
+ // the HAL queued the callback.
+ // TODO: rev the HAL so that it provides an interface name.
+
+ // Fetch current stats, so that when our notification reaches
+ // NetworkStatsService and triggers a poll, we will respond with
+ // current data (which will be above the limit that was reached).
+ // Note that if we just changed upstream, this is unnecessary but harmless.
+ // The stats for the previous upstream were already updated on this thread
+ // just after the upstream was changed, so they are also up-to-date.
+ updateStatsForCurrentUpstream();
+
+ try {
+ mNms.tetherLimitReached(mStatsProvider);
+ } catch (RemoteException e) {
+ mLog.e("Cannot report data limit reached: " + e);
+ }
}
@Override
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index d29a94b..54addb1 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -85,6 +85,8 @@
ArgumentCaptor.forClass(ArrayList.class);
private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+ private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
+ ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
private MockContentResolver mContentResolver;
@Before public void setUp() {
@@ -105,7 +107,7 @@
private void setupFunctioningHardwareInterface() {
when(mHardware.initOffloadConfig()).thenReturn(true);
- when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
+ when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
.thenReturn(true);
when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
}
@@ -493,4 +495,17 @@
waitForIdle();
inOrder.verify(mHardware).stopOffloadControl();
}
+
+ @Test
+ public void testDataLimitCallback() throws Exception {
+ setupFunctioningHardwareInterface();
+ enableOffload();
+
+ final OffloadController offload = makeOffloadController();
+ offload.start();
+
+ OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+ callback.onStoppedLimitReached();
+ verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+ }
}