Merge "Rate limit BatteryStats' ModemActivityInfo querying"
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8510bd3..1e5b498 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1331,6 +1331,13 @@
     LongSamplingCounter mMobileRadioActiveUnknownTime;
     LongSamplingCounter mMobileRadioActiveUnknownCount;
 
+    /**
+     * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+     * after it was last updated.
+     */
+    @VisibleForTesting
+    protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     @GuardedBy("this")
@@ -5531,6 +5538,15 @@
             } else {
                 mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+
+                if (mLastModemActivityInfo != null) {
+                    if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+                            + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
+                        // Modem Activity info has been collected recently, don't bother
+                        // triggering another update.
+                        return false;
+                    }
+                }
                 // Tell the caller to collect radio network/power stats.
                 return true;
             }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 0a1e3c7..e9c5b01 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -47,6 +47,7 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.util.MutableInt;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
@@ -83,6 +84,7 @@
  *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
 public class BatteryStatsNoteTest extends TestCase {
+    private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
     private static final int UID = 10500;
     private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
@@ -2030,6 +2032,113 @@
                 noRadioProcFlags, lastProcStateChangeFlags.value);
     }
 
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is still on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 2001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from HIGH.",
+                update);
+
+        // Note mobile radio is off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 3001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW.",
+                update);
+
+        // Note mobile radio is still off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 4001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from LOW.",
+                update);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 5001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state changes from LOW to HIGH.",
+                update);
+    }
+
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked_rateLimited() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setPowerProfile(mock(PowerProfile.class));
+
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+
+        final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit();
+        if (rateLimit < 0) {
+            Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = "
+                    + rateLimit);
+            return;
+        }
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        clocks.realtime = clocks.uptime = 2001;
+        mai.setTimestamp(clocks.realtime);
+        bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE,
+                clocks.realtime, clocks.uptime, mNetworkStatsManager);
+
+        // Note mobile radio is off within the rate limit duration.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state so soon after a noteModemControllerActivity",
+                update);
+
+        // Note mobile radio is on.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is off much later
+        clocks.realtime = clocks.uptime = clocks.realtime + rateLimit;
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW much later after a "
+                        + "noteModemControllerActivity.",
+                update);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 570b2ee..df4b896 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -113,6 +113,10 @@
         return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
     }
 
+    public long getMobileRadioPowerStateUpdateRateLimit() {
+        return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
+    }
+
     public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
         mNetworkStats = networkStats;
         return this;