Merge "[BOT.13] Make offload coordinator poll interval configurable" into rvc-dev
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 12464e1..20f30ea 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -25,6 +25,8 @@
 import static android.net.NetworkStats.UID_TETHERING;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 
+import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
+
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
 import android.net.MacAddress;
@@ -69,8 +71,6 @@
 public class BpfCoordinator {
     private static final String TAG = BpfCoordinator.class.getSimpleName();
     private static final int DUMP_TIMEOUT_MS = 10_000;
-    @VisibleForTesting
-    static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.
 
     @VisibleForTesting
     enum StatsType {
@@ -154,14 +154,6 @@
 
     @VisibleForTesting
     public abstract static class Dependencies {
-        /**
-         * Get polling Interval in milliseconds.
-         */
-        public int getPerformPollInterval() {
-            // TODO: Consider make this configurable.
-            return DEFAULT_PERFORM_POLL_INTERVAL_MS;
-        }
-
         /** Get handler. */
         @NonNull public abstract Handler getHandler();
 
@@ -403,6 +395,7 @@
             pw.println("Stats provider " + (mStatsProvider != null
                     ? "registered" : "not registered"));
             pw.println("Upstream quota: " + mInterfaceQuotas.toString());
+            pw.println("Polling interval: " + getPollingInterval() + " ms");
 
             pw.println("Forwarding stats:");
             pw.increaseIndent();
@@ -745,6 +738,17 @@
         updateQuotaAndStatsFromSnapshot(tetherStatsList);
     }
 
+    @VisibleForTesting
+    int getPollingInterval() {
+        // The valid range of interval is DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
+        // Ignore the config value is less than the minimum polling interval. Note that the
+        // minimum interval definition is invoked as OffloadController#isPollingStatsNeeded does.
+        // TODO: Perhaps define a minimum polling interval constant.
+        final TetheringConfiguration config = mDeps.getTetherConfig();
+        final int configInterval = (config != null) ? config.getOffloadPollInterval() : 0;
+        return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval);
+    }
+
     private void maybeSchedulePollingStats() {
         if (!mPollingStarted) return;
 
@@ -752,7 +756,7 @@
             mHandler.removeCallbacks(mScheduledPollingTask);
         }
 
-        mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
+        mHandler.postDelayed(mScheduledPollingTask, getPollingInterval());
     }
 
     // Return forwarding rule map. This is used for testing only.
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 31d9814..64242ae 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -25,11 +25,10 @@
 import static android.net.NetworkStats.UID_TETHERING;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 
-import static com.android.networkstack.tethering.BpfCoordinator
-        .DEFAULT_PERFORM_POLL_INTERVAL_MS;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -104,11 +103,6 @@
     private final TestLooper mTestLooper = new TestLooper();
     private BpfCoordinator.Dependencies mDeps =
             new BpfCoordinator.Dependencies() {
-            @Override
-            public int getPerformPollInterval() {
-                return DEFAULT_PERFORM_POLL_INTERVAL_MS;
-            }
-
             @NonNull
             public Handler getHandler() {
                 return new Handler(mTestLooper.getLooper());
@@ -183,9 +177,11 @@
         return parcel;
     }
 
+    // Set up specific tether stats list and wait for the stats cache is updated by polling thread
+    // in the coordinator. Beware of that it is only used for the default polling interval.
     private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception {
         when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList);
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
     }
 
@@ -254,7 +250,7 @@
         clearInvocations(mNetd);
 
         // Verify the polling update thread stopped.
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
         verify(mNetd, never()).tetherOffloadGetStats();
     }
@@ -279,20 +275,20 @@
         when(mNetd.tetherOffloadGetStats()).thenReturn(
                 new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)});
         mTetherStatsProvider.onSetAlert(100);
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
         mTetherStatsProviderCb.assertNoCallback();
 
         // Verify that notifyAlertReached fired when quota is reached.
         when(mNetd.tetherOffloadGetStats()).thenReturn(
                 new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 50, 0, 50, 0)});
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
         mTetherStatsProviderCb.expectNotifyAlertReached();
 
         // Verify that set quota with UNLIMITED won't trigger any callback.
         mTetherStatsProvider.onSetAlert(QUOTA_UNLIMITED);
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
         mTetherStatsProviderCb.assertNoCallback();
     }
@@ -512,7 +508,7 @@
         coordinator.startPolling();
 
         // The tether stats polling task should not be scheduled.
-        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         waitForIdle();
         verify(mNetd, never()).tetherOffloadGetStats();
 
@@ -559,4 +555,53 @@
         assertNotNull(rules);
         assertEquals(1, rules.size());
     }
+
+    @Test
+    public void testTetheringConfigSetPollingInterval() throws Exception {
+        setupFunctioningNetdInterface();
+
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+
+        // [1] The default polling interval.
+        coordinator.startPolling();
+        assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
+        coordinator.stopPolling();
+
+        // [2] Expect the invalid polling interval isn't applied. The valid range of interval is
+        // DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
+        for (final int interval
+                : new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) {
+            when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
+            coordinator.startPolling();
+            assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
+            coordinator.stopPolling();
+        }
+
+        // [3] Set a specific polling interval which is larger than default value.
+        // Use a large polling interval to avoid flaky test because the time forwarding
+        // approximation is used to verify the scheduled time of the polling thread.
+        final int pollingInterval = 100_000;
+        when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval);
+        coordinator.startPolling();
+
+        // Expect the specific polling interval to be applied.
+        assertEquals(pollingInterval, coordinator.getPollingInterval());
+
+        // Start on a new polling time slot.
+        mTestLooper.moveTimeForward(pollingInterval);
+        waitForIdle();
+        clearInvocations(mNetd);
+
+        // Move time forward to 90% polling interval time. Expect that the polling thread has not
+        // scheduled yet.
+        mTestLooper.moveTimeForward((long) (pollingInterval * 0.9));
+        waitForIdle();
+        verify(mNetd, never()).tetherOffloadGetStats();
+
+        // Move time forward to the remaining 10% polling interval time. Expect that the polling
+        // thread has scheduled.
+        mTestLooper.moveTimeForward((long) (pollingInterval * 0.1));
+        waitForIdle();
+        verify(mNetd).tetherOffloadGetStats();
+    }
 }