Merge "Reduce unnecessary overhead in CpuWakeupStats" into udc-dev
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
index 8b3667e..2f894b9 100644
--- a/core/res/res/xml/irq_device_map.xml
+++ b/core/res/res/xml/irq_device_map.xml
@@ -28,6 +28,8 @@
             - Wifi: Use this to denote network traffic that uses the wifi transport.
             - Sound_trigger: Use this to denote sound phrase detection, like the ones supported by
         SoundTriggerManager.
+            - Sensor: Use this to denote wakeups due to sensor events.
+            - Cellular_data: Use this to denote network traffic on the cellular transport.
 
         The overlay should use tags <device> and <subsystem> to describe this mapping in the
         following way:
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 73ab782..712a696 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -23,6 +23,7 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Handler;
@@ -48,6 +49,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.LongSupplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -62,15 +64,14 @@
     private static final String SUBSYSTEM_SENSOR_STRING = "Sensor";
     private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data";
     private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
-    @VisibleForTesting
-    static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+
     private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
 
     private final Handler mHandler;
     private final IrqDeviceMap mIrqDeviceMap;
     @VisibleForTesting
     final Config mConfig = new Config();
-    private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
+    private final WakingActivityHistory mRecentWakingActivity;
 
     @VisibleForTesting
     final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
@@ -85,6 +86,8 @@
 
     public CpuWakeupStats(Context context, int mapRes, Handler handler) {
         mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+        mRecentWakingActivity = new WakingActivityHistory(
+                () -> mConfig.WAKING_ACTIVITY_RETENTION_MS);
         mHandler = handler;
     }
 
@@ -202,15 +205,14 @@
     /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
     public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
             String rawReason) {
-        final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime);
+        final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime,
+                mIrqDeviceMap);
         if (parsedWakeup == null) {
+            // This wakeup is unsupported for attribution. Exit.
             return;
         }
         mWakeupEvents.put(elapsedRealtime, parsedWakeup);
         attemptAttributionFor(parsedWakeup);
-        // Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order,
-        // we can delete all history that will not be useful in attributing future wakeups.
-        mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
 
         // Limit history of wakeups and their attribution to the last retentionDuration. Note that
         // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
@@ -244,25 +246,22 @@
     }
 
     private synchronized void attemptAttributionFor(Wakeup wakeup) {
-        final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
-        if (subsystems == null) {
-            // We don't support attribution for this kind of wakeup yet.
-            return;
-        }
+        final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems;
 
         SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
         if (attribution == null) {
             attribution = new SparseArray<>();
             mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
         }
+        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
 
         for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) {
             final int subsystem = subsystems.keyAt(subsystemIdx);
 
-            // Blame all activity that happened WAKEUP_REASON_HALF_WINDOW_MS before or after
+            // Blame all activity that happened matchingWindowMillis before or after
             // the wakeup from each responsible subsystem.
-            final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
-            final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
+            final long startTime = wakeup.mElapsedMillis - matchingWindowMillis;
+            final long endTime = wakeup.mElapsedMillis + matchingWindowMillis;
 
             final SparseIntArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
                     startTime, endTime);
@@ -272,18 +271,16 @@
 
     private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
             SparseIntArray uidProcStates) {
+        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
+
         final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
-                activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
+                activityElapsed - matchingWindowMillis);
         final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
-                activityElapsed + WAKEUP_REASON_HALF_WINDOW_MS);
+                activityElapsed + matchingWindowMillis);
 
         for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
             final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx);
-            final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
-            if (subsystems == null) {
-                // Unsupported for attribution
-                continue;
-            }
+            final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems;
             if (subsystems.get(subsystem)) {
                 // We don't expect more than one wakeup to be found within such a short window, so
                 // just attribute this one and exit
@@ -405,11 +402,13 @@
      */
     @VisibleForTesting
     static final class WakingActivityHistory {
-        private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
-
+        private LongSupplier mRetentionSupplier;
         @VisibleForTesting
-        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
-                new SparseArray<>();
+        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();
+
+        WakingActivityHistory(LongSupplier retentionSupplier) {
+            mRetentionSupplier = retentionSupplier;
+        }
 
         void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) {
             if (uidProcStates == null) {
@@ -433,27 +432,13 @@
                     }
                 }
             }
-            // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
-            // Note that the last activity is always present, even if it occurred before
-            // WAKING_ACTIVITY_RETENTION_MS.
+            // Limit activity history per subsystem to the last retention period as supplied by
+            // mRetentionSupplier. Note that the last activity is always present, even if it
+            // occurred before the retention period.
             final int endIdx = wakingActivity.closestIndexOnOrBefore(
-                    elapsedRealtime - WAKING_ACTIVITY_RETENTION_MS);
+                    elapsedRealtime - mRetentionSupplier.getAsLong());
             for (int i = endIdx; i >= 0; i--) {
-                wakingActivity.removeAt(endIdx);
-            }
-        }
-
-        void clearAllBefore(long elapsedRealtime) {
-            for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
-                final TimeSparseArray<SparseIntArray> activityPerSubsystem =
-                        mWakingActivity.valueAt(subsystemIdx);
-                final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
-                for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
-                    activityPerSubsystem.removeAt(removeIdx);
-                }
-                // Generally waking activity is a high frequency occurrence for any subsystem, so we
-                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
-                // This will leave one TimeSparseArray per subsystem, which are few right now.
+                wakingActivity.removeAt(i);
             }
         }
 
@@ -515,33 +500,6 @@
         }
     }
 
-    private SparseBooleanArray getResponsibleSubsystemsForWakeup(Wakeup wakeup) {
-        if (ArrayUtils.isEmpty(wakeup.mDevices)) {
-            return null;
-        }
-        final SparseBooleanArray result = new SparseBooleanArray();
-        for (final Wakeup.IrqDevice device : wakeup.mDevices) {
-            final List<String> rawSubsystems = mIrqDeviceMap.getSubsystemsForDevice(device.mDevice);
-
-            boolean anyKnownSubsystem = false;
-            if (rawSubsystems != null) {
-                for (int i = 0; i < rawSubsystems.size(); i++) {
-                    final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
-                    if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
-                        // Just in case the xml had arbitrary subsystem names, we want to make sure
-                        // that we only put the known ones into our attribution map.
-                        result.put(subsystem, true);
-                        anyKnownSubsystem = true;
-                    }
-                }
-            }
-            if (!anyKnownSubsystem) {
-                result.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
-            }
-        }
-        return result;
-    }
-
     static int stringToKnownSubsystem(String rawSubsystem) {
         switch (rawSubsystem) {
             case SUBSYSTEM_ALARM_STRING:
@@ -598,15 +556,19 @@
         long mElapsedMillis;
         long mUptimeMillis;
         IrqDevice[] mDevices;
+        SparseBooleanArray mResponsibleSubsystems;
 
-        private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+        private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis,
+                SparseBooleanArray responsibleSubsystems) {
             mType = type;
             mDevices = devices;
             mElapsedMillis = elapsedMillis;
             mUptimeMillis = uptimeMillis;
+            mResponsibleSubsystems = responsibleSubsystems;
         }
 
-        static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
+        static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis,
+                IrqDeviceMap deviceMap) {
             final String[] components = rawReason.split(":");
             if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) {
                 // Accounting of aborts is not supported yet.
@@ -616,6 +578,7 @@
             int type = TYPE_IRQ;
             int parsedDeviceCount = 0;
             final IrqDevice[] parsedDevices = new IrqDevice[components.length];
+            final SparseBooleanArray responsibleSubsystems = new SparseBooleanArray();
 
             for (String component : components) {
                 final Matcher matcher = sIrqPattern.matcher(component.trim());
@@ -635,13 +598,35 @@
                         continue;
                     }
                     parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device);
+
+                    final List<String> rawSubsystems = deviceMap.getSubsystemsForDevice(device);
+                    boolean anyKnownSubsystem = false;
+                    if (rawSubsystems != null) {
+                        for (int i = 0; i < rawSubsystems.size(); i++) {
+                            final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
+                            if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
+                                // Just in case the xml had arbitrary subsystem names, we want to
+                                // make sure that we only put the known ones into our map.
+                                responsibleSubsystems.put(subsystem, true);
+                                anyKnownSubsystem = true;
+                            }
+                        }
+                    }
+                    if (!anyKnownSubsystem) {
+                        responsibleSubsystems.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
+                    }
                 }
             }
             if (parsedDeviceCount == 0) {
                 return null;
             }
+            if (responsibleSubsystems.size() == 1 && responsibleSubsystems.get(
+                    CPU_WAKEUP_SUBSYSTEM_UNKNOWN, false)) {
+                // There is no attributable subsystem here, so we do not support it.
+                return null;
+            }
             return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
-                    uptimeMillis);
+                    uptimeMillis, responsibleSubsystems);
         }
 
         @Override
@@ -651,6 +636,7 @@
                     + ", mElapsedMillis=" + mElapsedMillis
                     + ", mUptimeMillis=" + mUptimeMillis
                     + ", mDevices=" + Arrays.toString(mDevices)
+                    + ", mResponsibleSubsystems=" + mResponsibleSubsystems
                     + '}';
         }
 
@@ -672,18 +658,28 @@
 
     static final class Config implements DeviceConfig.OnPropertiesChangedListener {
         static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms";
+        static final String KEY_WAKEUP_MATCHING_WINDOW_MS = "wakeup_matching_window_ms";
+        static final String KEY_WAKING_ACTIVITY_RETENTION_MS = "waking_activity_retention_ms";
 
         private static final String[] PROPERTY_NAMES = {
                 KEY_WAKEUP_STATS_RETENTION_MS,
+                KEY_WAKEUP_MATCHING_WINDOW_MS,
+                KEY_WAKING_ACTIVITY_RETENTION_MS,
         };
 
         static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3);
+        private static final long DEFAULT_WAKEUP_MATCHING_WINDOW_MS = TimeUnit.SECONDS.toMillis(1);
+        private static final long DEFAULT_WAKING_ACTIVITY_RETENTION_MS =
+                TimeUnit.MINUTES.toMillis(5);
 
         /**
          * Wakeup stats are retained only for this duration.
          */
         public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS;
+        public volatile long WAKEUP_MATCHING_WINDOW_MS = DEFAULT_WAKEUP_MATCHING_WINDOW_MS;
+        public volatile long WAKING_ACTIVITY_RETENTION_MS = DEFAULT_WAKING_ACTIVITY_RETENTION_MS;
 
+        @SuppressLint("MissingPermission")
         void register(Executor executor) {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
                     executor, this);
@@ -702,6 +698,15 @@
                         WAKEUP_STATS_RETENTION_MS = properties.getLong(
                                 KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS);
                         break;
+                    case KEY_WAKEUP_MATCHING_WINDOW_MS:
+                        WAKEUP_MATCHING_WINDOW_MS = properties.getLong(
+                                KEY_WAKEUP_MATCHING_WINDOW_MS, DEFAULT_WAKEUP_MATCHING_WINDOW_MS);
+                        break;
+                    case KEY_WAKING_ACTIVITY_RETENTION_MS:
+                        WAKING_ACTIVITY_RETENTION_MS = properties.getLong(
+                                KEY_WAKING_ACTIVITY_RETENTION_MS,
+                                DEFAULT_WAKING_ACTIVITY_RETENTION_MS);
+                        break;
                 }
             }
         }
@@ -711,7 +716,19 @@
 
             pw.increaseIndent();
 
-            pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS);
+            pw.print(KEY_WAKEUP_STATS_RETENTION_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKEUP_STATS_RETENTION_MS, pw);
+            pw.println();
+
+            pw.print(KEY_WAKEUP_MATCHING_WINDOW_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKEUP_MATCHING_WINDOW_MS, pw);
+            pw.println();
+
+            pw.print(KEY_WAKING_ACTIVITY_RETENTION_MS);
+            pw.print("=");
+            TimeUtils.formatDuration(WAKING_ACTIVITY_RETENTION_MS, pw);
             pw.println();
 
             pw.decreaseIndent();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 76b6a82..d181419 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -23,8 +23,6 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
-import static com.android.server.power.stats.wakeups.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -55,7 +53,7 @@
     private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device";
     private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device";
     private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
-    private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
+    private static final String KERNEL_REASON_UNKNOWN_FORMAT = "free-form-reason test.alarm.device";
     private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
     private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device";
 
@@ -85,30 +83,29 @@
 
     @Test
     public void removesOldWakeups() {
-        // The xml resource doesn't matter for this test.
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS;
 
         final Set<Long> timestamps = new HashSet<>();
         final long firstWakeup = 453192;
 
-        obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ);
+        // Reasons do not matter for this test, as long as they map to known subsystems
+        obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_ALARM_IRQ);
         timestamps.add(firstWakeup);
         for (int i = 1; i < 1000; i++) {
             final long delta = mRandom.nextLong(retention);
             if (timestamps.add(firstWakeup + delta)) {
-                obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ);
+                obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_SENSOR_IRQ);
             }
         }
         assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
 
-        obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231,
-                KERNEL_REASON_UNKNOWN_IRQ);
+        obj.noteWakeupTimeAndReason(firstWakeup + retention, 231, KERNEL_REASON_WIFI_IRQ);
         assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
 
         for (int i = 0; i < 100; i++) {
             final long now = mRandom.nextLong(retention + 1, 100 * retention);
-            obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ);
+            obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_SOUND_TRIGGER_IRQ);
             assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
         }
     }
@@ -201,9 +198,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
 
@@ -234,9 +231,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, wakeupTime + 5, TEST_UID_3,
                 TEST_UID_5);
@@ -268,9 +265,9 @@
 
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 3, TEST_UID_4, TEST_UID_5);
 
@@ -300,9 +297,9 @@
         // Alarm activity
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
@@ -311,9 +308,9 @@
         // Wifi activity
         // Outside the window, so should be ignored.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_4);
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
-                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_3);
+                wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_3);
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_2,
@@ -347,33 +344,67 @@
     }
 
     @Test
-    public void unknownIrqAttribution() {
+    public void unknownIrqSoloIgnored() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 92123410;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
 
-        assertThat(obj.mWakeupEvents.size()).isEqualTo(1);
+        assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
 
-        // Unrelated subsystems, should not be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 3, TEST_UID_4,
                 TEST_UID_5);
 
-        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
-        assertThat(attribution).isNotNull();
-        assertThat(attribution.size()).isEqualTo(1);
-        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
-        final SparseIntArray uidProcStates = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
-        assertThat(uidProcStates == null || uidProcStates.size() == 0).isTrue();
+        // Any nearby activity should not end up in the attribution map.
+        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
     }
 
     @Test
-    public void unknownWakeupIgnored() {
+    public void unknownAndWifiAttribution() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+        final long wakeupTime = 92123410;
+
+        populateDefaultProcStates(obj);
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 24,
+                KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_WIFI_IRQ);
+
+        // Wifi activity
+        // Outside the window, so should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+                wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
+        // Should be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_3,
+                TEST_UID_5);
+
+        // Unrelated, should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(2);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(
+                TEST_PROC_STATE_1);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_2)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_4)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isNull();
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse();
+    }
+
+    @Test
+    public void unknownFormatWakeupIgnored() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 72123210;
 
-        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
+        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN_FORMAT);
 
         // Should be ignored as this type of wakeup is not known.
         assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
index cba7dbe..b02618e 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
@@ -28,8 +28,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.ThreadLocalRandom;
+
 @RunWith(AndroidJUnit4.class)
 public class WakingActivityHistoryTest {
+    private volatile long mTestRetention = 54;
 
     private static boolean areSame(SparseIntArray a, SparseIntArray b) {
         if (a == b) {
@@ -55,7 +58,7 @@
 
     @Test
     public void recordActivityAppendsUids() {
-        final WakingActivityHistory history = new WakingActivityHistory();
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
         final int subsystem = 42;
         final long timestamp = 54;
 
@@ -100,7 +103,7 @@
 
     @Test
     public void recordActivityDoesNotDeleteExistingUids() {
-        final WakingActivityHistory history = new WakingActivityHistory();
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
         final int subsystem = 42;
         long timestamp = 101;
 
@@ -151,4 +154,120 @@
         assertThat(recordedUids.get(62, -1)).isEqualTo(31);
         assertThat(recordedUids.get(85, -1)).isEqualTo(39);
     }
+
+    @Test
+    public void removeBetween() {
+        final WakingActivityHistory history = new WakingActivityHistory(() -> Long.MAX_VALUE);
+
+        final int subsystem = 43;
+
+        final SparseIntArray uids = new SparseIntArray();
+        uids.put(1, 17);
+        uids.put(15, 2);
+        uids.put(62, 31);
+        history.recordActivity(subsystem, 123, uids);
+
+        uids.put(54, 91);
+        history.recordActivity(subsystem, 150, uids);
+
+        uids.put(101, 32);
+        uids.delete(1);
+        history.recordActivity(subsystem, 191, uids);
+
+        SparseIntArray removedUids = history.removeBetween(subsystem, 100, 122);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 124, 149);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 151, 190);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 192, 240);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+
+        // Removing from a different subsystem should do nothing.
+        removedUids = history.removeBetween(subsystem + 1, 0, 300);
+        assertThat(removedUids).isNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(3);
+
+        removedUids = history.removeBetween(subsystem, 0, 300);
+        assertThat(removedUids.size()).isEqualTo(5);
+        assertThat(removedUids.get(1, -1)).isEqualTo(17);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(0);
+
+        history.recordActivity(subsystem, 23, uids);
+        uids.put(31, 123);
+        history.recordActivity(subsystem, 49, uids);
+        uids.put(177, 432);
+        history.recordActivity(subsystem, 89, uids);
+
+        removedUids = history.removeBetween(subsystem, 23, 23);
+        assertThat(removedUids.size()).isEqualTo(4);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(2);
+
+        removedUids = history.removeBetween(subsystem, 49, 54);
+        assertThat(removedUids.size()).isEqualTo(5);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+        assertThat(removedUids.get(31, -1)).isEqualTo(123);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(1);
+
+        removedUids = history.removeBetween(subsystem, 23, 89);
+        assertThat(removedUids.size()).isEqualTo(6);
+        assertThat(removedUids.get(15, -1)).isEqualTo(2);
+        assertThat(removedUids.get(62, -1)).isEqualTo(31);
+        assertThat(removedUids.get(54, -1)).isEqualTo(91);
+        assertThat(removedUids.get(101, -1)).isEqualTo(32);
+        assertThat(removedUids.get(31, -1)).isEqualTo(123);
+        assertThat(removedUids.get(177, -1)).isEqualTo(432);
+
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void deletesActivityPastRetention() {
+        final WakingActivityHistory history = new WakingActivityHistory(() -> mTestRetention);
+        final int subsystem = 49;
+
+        mTestRetention = 454;
+
+        final long firstTime = 342;
+        for (int i = 0; i < mTestRetention; i++) {
+            history.recordActivity(subsystem, firstTime + i, new SparseIntArray());
+        }
+        assertThat(history.mWakingActivity.get(subsystem)).isNotNull();
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(mTestRetention);
+
+        history.recordActivity(subsystem, firstTime + mTestRetention + 7, new SparseIntArray());
+        assertThat(history.mWakingActivity.get(subsystem).size()).isEqualTo(mTestRetention - 7);
+
+        final ThreadLocalRandom random = ThreadLocalRandom.current();
+
+        for (int i = 0; i < 100; i++) {
+            final long time = random.nextLong(firstTime + mTestRetention + 100,
+                    456 * mTestRetention);
+            history.recordActivity(subsystem, time, new SparseIntArray());
+            assertThat(history.mWakingActivity.get(subsystem).closestIndexOnOrBefore(
+                    time - mTestRetention)).isLessThan(0);
+        }
+    }
 }