Accumulate battery usage stats incrementally

Process just the yet-unprocessed span of battery history
on each accumulation pass

Bug: 345022340
Test: atest PowerStatsTests; atest PowerStatsTestsRavenwood
Flag: com.android.server.power.optimization.accumulate_battery_usage_stats
Change-Id: I2829c830acac26705176258b0e02e1b5e5df3e4a
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index a698b9d..ddcb5c6 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -1031,7 +1031,10 @@
             return this;
         }
 
-        private long getStatsDuration() {
+        /**
+         * Returns the duration of the battery session reflected by these stats.
+         */
+        public long getStatsDuration() {
             if (mStatsDurationMs != -1) {
                 return mStatsDurationMs;
             } else {
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index b533225..e68c4ca 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.util.IntArray;
 
+import com.android.internal.os.MonotonicClock;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -85,8 +87,11 @@
     @NonNull
     private final int[] mUserIds;
     private final long mMaxStatsAgeMs;
-    private final long mFromTimestamp;
-    private final long mToTimestamp;
+
+    private final long mAggregatedFromTimestamp;
+    private final long mAggregatedToTimestamp;
+    private long mMonotonicStartTime;
+    private long mMonotonicEndTime;
     private final double mMinConsumedPowerThreshold;
     private final @BatteryConsumer.PowerComponentId int[] mPowerComponents;
 
@@ -96,8 +101,10 @@
                 : new int[]{UserHandle.USER_ALL};
         mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
         mMinConsumedPowerThreshold = builder.mMinConsumedPowerThreshold;
-        mFromTimestamp = builder.mFromTimestamp;
-        mToTimestamp = builder.mToTimestamp;
+        mAggregatedFromTimestamp = builder.mAggregateFromTimestamp;
+        mAggregatedToTimestamp = builder.mAggregateToTimestamp;
+        mMonotonicStartTime = builder.mMonotonicStartTime;
+        mMonotonicEndTime = builder.mMonotonicEndTime;
         mPowerComponents = builder.mPowerComponents;
     }
 
@@ -163,11 +170,27 @@
     }
 
     /**
-     * Returns the exclusive lower bound of the stored snapshot timestamps that should be included
-     * in the aggregation.  Ignored if {@link #getToTimestamp()} is zero.
+     * Returns the exclusive lower bound of the battery history that should be included in
+     * the aggregated battery usage stats.
      */
-    public long getFromTimestamp() {
-        return mFromTimestamp;
+    public long getMonotonicStartTime() {
+        return mMonotonicStartTime;
+    }
+
+    /**
+     * Returns the inclusive upper bound of the battery history that should be included in
+     * the aggregated battery usage stats.
+     */
+    public long getMonotonicEndTime() {
+        return mMonotonicEndTime;
+    }
+
+    /**
+     * Returns the exclusive lower bound of the stored snapshot timestamps that should be included
+     * in the aggregation.  Ignored if {@link #getAggregatedToTimestamp()} is zero.
+     */
+    public long getAggregatedFromTimestamp() {
+        return mAggregatedFromTimestamp;
     }
 
     /**
@@ -175,8 +198,8 @@
      * be included in the aggregation.  The default is to include only the current stats
      * accumulated since the latest battery reset.
      */
-    public long getToTimestamp() {
-        return mToTimestamp;
+    public long getAggregatedToTimestamp() {
+        return mAggregatedToTimestamp;
     }
 
     private BatteryUsageStatsQuery(Parcel in) {
@@ -185,8 +208,8 @@
         in.readIntArray(mUserIds);
         mMaxStatsAgeMs = in.readLong();
         mMinConsumedPowerThreshold = in.readDouble();
-        mFromTimestamp = in.readLong();
-        mToTimestamp = in.readLong();
+        mAggregatedFromTimestamp = in.readLong();
+        mAggregatedToTimestamp = in.readLong();
         mPowerComponents = in.createIntArray();
     }
 
@@ -197,8 +220,8 @@
         dest.writeIntArray(mUserIds);
         dest.writeLong(mMaxStatsAgeMs);
         dest.writeDouble(mMinConsumedPowerThreshold);
-        dest.writeLong(mFromTimestamp);
-        dest.writeLong(mToTimestamp);
+        dest.writeLong(mAggregatedFromTimestamp);
+        dest.writeLong(mAggregatedToTimestamp);
         dest.writeIntArray(mPowerComponents);
     }
 
@@ -228,8 +251,10 @@
         private int mFlags;
         private IntArray mUserIds;
         private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
-        private long mFromTimestamp;
-        private long mToTimestamp;
+        private long mMonotonicStartTime = MonotonicClock.UNDEFINED;
+        private long mMonotonicEndTime = MonotonicClock.UNDEFINED;
+        private long mAggregateFromTimestamp;
+        private long mAggregateToTimestamp;
         private double mMinConsumedPowerThreshold = 0;
         private @BatteryConsumer.PowerComponentId int[] mPowerComponents;
 
@@ -241,6 +266,17 @@
         }
 
         /**
+         * Specifies the time range for the requested stats, in terms of MonotonicClock
+         * @param monotonicStartTime Inclusive starting monotonic timestamp
+         * @param monotonicEndTime Exclusive ending timestamp. Can be MonotonicClock.UNDEFINED
+         */
+        public Builder monotonicTimeRange(long monotonicStartTime, long monotonicEndTime) {
+            mMonotonicStartTime = monotonicStartTime;
+            mMonotonicEndTime = monotonicEndTime;
+            return this;
+        }
+
+        /**
          * Add a user whose battery stats should be included in the battery usage stats.
          * {@link UserHandle#USER_ALL} will be used by default if no users are added explicitly.
          */
@@ -345,8 +381,8 @@
          */
         // TODO(b/298459065): switch to monotonic clock
         public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
-            mFromTimestamp = fromTimestamp;
-            mToTimestamp = toTimestamp;
+            mAggregateFromTimestamp = fromTimestamp;
+            mAggregateToTimestamp = toTimestamp;
             return this;
         }
 
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 7f7ef04..f893739 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -109,9 +109,11 @@
      * Returns the amount of time in milliseconds this UID spent in the specified process state.
      */
     public long getTimeInProcessStateMs(@ProcessState int state) {
-        Key key = getKey(POWER_COMPONENT_BASE, state);
-        if (key != null) {
-            return getUsageDurationMillis(key);
+        if (state != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+            Key key = getKey(POWER_COMPONENT_BASE, state);
+            if (key != null) {
+                return getUsageDurationMillis(key);
+            }
         }
         return 0;
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d61785e..07aa720 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -83,7 +83,7 @@
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    private static final int VERSION = 210;
+    private static final int VERSION = 211;
 
     private static final String HISTORY_DIR = "battery-history";
     private static final String FILE_SUFFIX = ".bh";
@@ -210,6 +210,8 @@
     private final MonotonicClock mMonotonicClock;
     // Monotonic time when we started writing to the history buffer
     private long mHistoryBufferStartTime;
+    // Monotonically increasing size of written history
+    private long mMonotonicHistorySize;
     private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
     private byte mLastHistoryStepLevel = 0;
     private boolean mMutable = true;
@@ -909,6 +911,8 @@
                 }
                 // skip monotonic time field.
                 p.readLong();
+                // skip monotonic size field
+                p.readLong();
 
                 final int bufSize = p.readInt();
                 final int curPos = p.dataPosition();
@@ -964,6 +968,8 @@
         }
         // skip monotonic time field.
         out.readLong();
+        // skip monotonic size field
+        out.readLong();
         return true;
     }
 
@@ -987,6 +993,7 @@
         p.setDataPosition(0);
         p.readInt();        // Skip the version field
         long monotonicTime = p.readLong();
+        p.readLong();       // Skip monotonic size field
         p.setDataPosition(pos);
         return monotonicTime;
     }
@@ -1819,6 +1826,7 @@
             // as long as no bit has changed both between now and the last entry, as
             // well as the last entry and the one before it (so we capture any toggles).
             if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
+            mMonotonicHistorySize -= (mHistoryBuffer.dataSize() - mHistoryBufferLastPos);
             mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
             mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
             mHistoryBufferLastPos = -1;
@@ -1934,6 +1942,7 @@
         }
         mHistoryLastWritten.tagsFirstOccurrence = hasTags;
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+        mMonotonicHistorySize += (mHistoryBuffer.dataSize() - mHistoryBufferLastPos);
         cur.wakelockTag = null;
         cur.wakeReasonTag = null;
         cur.eventCode = HistoryItem.EVENT_NONE;
@@ -2344,6 +2353,8 @@
             }
 
             mHistoryBufferStartTime = in.readLong();
+            mMonotonicHistorySize = in.readLong();
+
             mHistoryBuffer.setDataSize(0);
             mHistoryBuffer.setDataPosition(0);
 
@@ -2370,6 +2381,7 @@
     private void writeHistoryBuffer(Parcel out) {
         out.writeInt(BatteryStatsHistory.VERSION);
         out.writeLong(mHistoryBufferStartTime);
+        out.writeLong(mMonotonicHistorySize);
         out.writeInt(mHistoryBuffer.dataSize());
         if (DEBUG) {
             Slog.i(TAG, "***************** WRITING HISTORY: "
@@ -2457,6 +2469,14 @@
     }
 
     /**
+     * Returns the monotonically increasing size of written history, including the buffers
+     * that have already been discarded.
+     */
+    public long getMonotonicHistorySize() {
+        return mMonotonicHistorySize;
+    }
+
+    /**
      * Prints battery stats history for debugging.
      */
     public void dump(PrintWriter pw, long startTimeMs, long endTimeMs) {
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 80cf088..9498273 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -45,7 +45,12 @@
     <integer name="config_powerStatsAggregationPeriod">14400000</integer>
 
     <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
-    history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+    history time for every aggregated power stats span that is stored in PowerStatsStore.
     It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
     <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
+
+    <!-- BatteryUsageStats accumulation period as determined by the size of accumulated
+    battery history, in bytes. -->
+    <integer name="config_accumulatedBatteryUsageStatsSpanSize">32768</integer>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c1893ab..1cb50f4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5287,6 +5287,7 @@
   <java-symbol type="string" name="config_powerStatsThrottlePeriods" />
   <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
   <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
+  <java-symbol type="integer" name="config_accumulatedBatteryUsageStatsSpanSize" />
 
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4ac42ec..77ee279 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -196,6 +196,7 @@
     private final PowerAttributor mPowerAttributor;
 
     private volatile boolean mMonitorEnabled = true;
+    private boolean mRailsStatsCollectionEnabled = true;
 
     private native void getRailEnergyPowerStats(RailStats railStats);
     private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
@@ -312,8 +313,17 @@
         }
     }
 
+    public void setRailsStatsCollectionEnabled(boolean railsStatsCollectionEnabled) {
+        mRailsStatsCollectionEnabled = railsStatsCollectionEnabled;
+    }
+
     @Override
     public void fillRailDataStats(RailStats railStats) {
+        if (!mRailsStatsCollectionEnabled) {
+            railStats.setRailStatsAvailability(false);
+            return;
+        }
+
         if (DBG) Slog.d(TAG, "begin getRailEnergyPowerStats");
         try {
             getRailEnergyPowerStats(railStats);
@@ -423,7 +433,7 @@
         mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                 systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
                 mCpuScalingPolicies, mPowerStatsUidResolver);
-        mWorker = new BatteryExternalStatsWorker(context, mStats);
+        mWorker = new BatteryExternalStatsWorker(context, mStats, mHandler);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
@@ -436,9 +446,12 @@
                 mCpuScalingPolicies, () -> mStats.getBatteryCapacity(),
                 mPowerStatsUidResolver);
         mPowerStatsScheduler = createPowerStatsScheduler(mContext);
+
+        int accumulatedBatteryUsageStatsSpanSize = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_accumulatedBatteryUsageStatsSpanSize);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
                 mPowerAttributor, mPowerProfile, mCpuScalingPolicies,
-                mPowerStatsStore, Clock.SYSTEM_CLOCK);
+                mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK);
         mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
         mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
@@ -506,7 +519,7 @@
 
     public void systemServicesReady() {
         mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore,
-                Flags.accumulateBatteryUsageStats());
+                isBatteryUsageStatsAccumulationSupported());
 
         MultiStatePowerAttributor attributor = (MultiStatePowerAttributor) mPowerAttributor;
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU,
@@ -588,6 +601,12 @@
                 BatteryConsumer.POWER_COMPONENT_CAMERA,
                 Flags.streamlinedMiscBatteryStats());
 
+        // Currently unimplemented.
+        mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MEMORY,
+                Flags.streamlinedMiscBatteryStats());
+        attributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_MEMORY,
+                Flags.streamlinedMiscBatteryStats());
+
         // By convention POWER_COMPONENT_ANY represents custom Energy Consumers
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_ANY,
                 Flags.streamlinedMiscBatteryStats());
@@ -631,6 +650,13 @@
         registerStatsCallbacks();
     }
 
+    private static boolean isBatteryUsageStatsAccumulationSupported() {
+        return Flags.accumulateBatteryUsageStats()
+                && Flags.streamlinedBatteryStats()
+                && Flags.streamlinedConnectivityBatteryStats()
+                && Flags.streamlinedMiscBatteryStats();
+    }
+
     /**
      * Notifies BatteryStatsService that the system server is ready.
      */
@@ -776,7 +802,8 @@
 
     private void syncStats(String reason, int flags) {
         mStats.collectPowerStatsSamples();
-        awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+        mWorker.scheduleSync(reason, flags);
+        awaitCompletion();
     }
 
     private void awaitCompletion() {
@@ -1127,7 +1154,7 @@
                             .includeVirtualUids()
                             .setMinConsumedPowerThreshold(minConsumedPowerThreshold);
 
-                    if (Flags.accumulateBatteryUsageStats()) {
+                    if (isBatteryUsageStatsAccumulationSupported()) {
                         query.accumulated();
                     }
 
@@ -3046,7 +3073,7 @@
         if (Flags.streamlinedBatteryStats()) {
             pw.println("  --sample: collect and dump a sample of stats for debugging purpose");
         }
-        if (Flags.accumulateBatteryUsageStats()) {
+        if (isBatteryUsageStatsAccumulationSupported()) {
             pw.println("  --accumulated: continuously accumulated since setup or reset-all");
         }
         pw.println("  <package.name>: optional name of package to filter output by.");
@@ -3662,24 +3689,12 @@
                     android.Manifest.permission.BATTERY_STATS, null);
         }
 
-        Future future;
         if (shouldCollectExternalStats()) {
-            future = mWorker.scheduleSync("get-health-stats-for-uids",
+            mWorker.scheduleSync("get-health-stats-for-uids",
                     BatteryExternalStatsWorker.UPDATE_ALL);
-        } else {
-            future = null;
         }
 
         mHandler.post(() -> {
-            if (future != null) {
-                try {
-                    // Worker uses a separate thread pool, so waiting here won't cause a deadlock
-                    future.get();
-                } catch (InterruptedException | ExecutionException e) {
-                    Slog.e(TAG, "Sync failed", e);
-                }
-            }
-
             final long ident = Binder.clearCallingIdentity();
             int i = -1;
             try {
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 8311034..f90da64 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -27,12 +27,11 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.OutcomeReceiver;
 import android.os.Parcelable;
-import android.os.Process;
 import android.os.SynchronousResultReceiver;
 import android.os.SystemClock;
-import android.os.ThreadLocalWorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.power.PowerStatsInternal;
 import android.telephony.ModemActivityInfo;
@@ -50,11 +49,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -85,29 +80,23 @@
     // stop running.
     public static final int UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000;
 
-    private final ScheduledExecutorService mExecutorService =
-            Executors.newSingleThreadScheduledExecutor(
-                    (ThreadFactory) r -> {
-                        Thread t = new Thread(
-                                () -> {
-                                    ThreadLocalWorkSource.setUid(Process.myUid());
-                                    r.run();
-                                },
-                                "batterystats-worker");
-                        t.setPriority(Thread.NORM_PRIORITY);
-                        return t;
-                    });
+    // Various types of sync, passed to Handler
+    private static final int SYNC_UPDATE = 1;
+    private static final int SYNC_WAKELOCK_CHANGE = 2;
+    private static final int SYNC_BATTERY_LEVEL_CHANGE = 3;
+    private static final int SYNC_PROCESS_STATE_CHANGE = 4;
+    private static final int SYNC_USER_REMOVAL = 5;
+
+    private final Handler mHandler;
 
     @GuardedBy("mStats")
     private final BatteryStatsImpl mStats;
 
     @GuardedBy("this")
+    @ExternalUpdateFlag
     private int mUpdateFlags = 0;
 
     @GuardedBy("this")
-    private Future<?> mCurrentFuture = null;
-
-    @GuardedBy("this")
     private String mCurrentReason = null;
 
     @GuardedBy("this")
@@ -125,15 +114,6 @@
     @GuardedBy("this")
     private boolean mUseLatestStates = true;
 
-    @GuardedBy("this")
-    private Future<?> mWakelockChangesUpdate;
-
-    @GuardedBy("this")
-    private Future<?> mBatteryLevelSync;
-
-    @GuardedBy("this")
-    private Future<?> mProcessStateSync;
-
     // If both mStats and mWorkerLock need to be synchronized, mWorkerLock must be acquired first.
     private final Object mWorkerLock = new Object();
 
@@ -190,14 +170,15 @@
         }
     }
 
-    public BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
-        this(new Injector(context), stats);
+    public BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats, Handler handler) {
+        this(new Injector(context), stats, handler);
     }
 
     @VisibleForTesting
-    BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+    BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats, Handler handler) {
         mInjector = injector;
         mStats = stats;
+        mHandler = handler;
     }
 
     public void systemServicesReady() {
@@ -249,20 +230,20 @@
     }
 
     @Override
-    public synchronized Future<?> scheduleSync(String reason, int flags) {
-        return scheduleSyncLocked(reason, flags);
+    public synchronized void scheduleSync(String reason, @ExternalUpdateFlag int flags) {
+        scheduleSyncLocked(reason, flags);
     }
 
     @Override
-    public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
-        return scheduleSyncLocked("remove-uid", UPDATE_CPU);
+    public synchronized void scheduleCpuSyncDueToRemovedUid(int uid) {
+        scheduleSyncLocked("remove-uid", UPDATE_CPU);
     }
 
     @Override
-    public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+    public void scheduleSyncDueToScreenStateChange(@ExternalUpdateFlag int flags, boolean onBattery,
             boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
         synchronized (BatteryExternalStatsWorker.this) {
-            if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
+            if (!mHandler.hasMessages(SYNC_UPDATE) || (mUpdateFlags & UPDATE_CPU) == 0) {
                 mOnBattery = onBattery;
                 mOnBatteryScreenOff = onBatteryScreenOff;
                 mUseLatestStates = false;
@@ -270,91 +251,70 @@
             // always update screen state
             mScreenState = screenState;
             mPerDisplayScreenStates = perDisplayScreenStates;
-            return scheduleSyncLocked("screen-state", flags);
+            scheduleSyncLocked("screen-state", flags);
         }
     }
 
     @Override
-    public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
+    public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
         synchronized (BatteryExternalStatsWorker.this) {
-            mWakelockChangesUpdate = scheduleDelayedSyncLocked(mWakelockChangesUpdate,
+            scheduleDelayedSyncLocked(SYNC_WAKELOCK_CHANGE,
                     () -> {
                         scheduleSync("wakelock-change", UPDATE_CPU);
                         scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg());
                     },
                     delayMillis);
-            return mWakelockChangesUpdate;
         }
     }
 
     @Override
     public void cancelCpuSyncDueToWakelockChange() {
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (mWakelockChangesUpdate != null) {
-                mWakelockChangesUpdate.cancel(false);
-                mWakelockChangesUpdate = null;
-            }
-        }
+        mHandler.removeMessages(SYNC_WAKELOCK_CHANGE);
     }
 
     @Override
-    public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
+    public void scheduleSyncDueToBatteryLevelChange(long delayMillis) {
         synchronized (BatteryExternalStatsWorker.this) {
-            mBatteryLevelSync = scheduleDelayedSyncLocked(mBatteryLevelSync,
+            scheduleDelayedSyncLocked(SYNC_BATTERY_LEVEL_CHANGE,
                     () -> scheduleSync("battery-level", UPDATE_ALL),
                     delayMillis);
-            return mBatteryLevelSync;
         }
     }
 
     @GuardedBy("this")
     private void cancelSyncDueToBatteryLevelChangeLocked() {
-        if (mBatteryLevelSync != null) {
-            mBatteryLevelSync.cancel(false);
-            mBatteryLevelSync = null;
-        }
+        mHandler.removeMessages(SYNC_BATTERY_LEVEL_CHANGE);
     }
 
     @Override
     public void scheduleSyncDueToProcessStateChange(int flags, long delayMillis) {
         synchronized (BatteryExternalStatsWorker.this) {
-            mProcessStateSync = scheduleDelayedSyncLocked(mProcessStateSync,
+            scheduleDelayedSyncLocked(SYNC_PROCESS_STATE_CHANGE,
                     () -> scheduleSync("procstate-change", flags),
                     delayMillis);
         }
     }
 
     public void cancelSyncDueToProcessStateChange() {
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (mProcessStateSync != null) {
-                mProcessStateSync.cancel(false);
-                mProcessStateSync = null;
-            }
-        }
+        mHandler.removeMessages(SYNC_PROCESS_STATE_CHANGE);
     }
 
     @Override
-    public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
-        synchronized (BatteryExternalStatsWorker.this) {
-            try {
-                // Initial quick clean-up after a user removal
-                mExecutorService.schedule(() -> {
-                    synchronized (mStats) {
-                        mStats.clearRemovedUserUidsLocked(userId);
-                    }
-                }, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
-
-                // Final clean-up after a user removal, to take care of UIDs that were running
-                // longer than expected
-                return mExecutorService.schedule(() -> {
-                    synchronized (mStats) {
-                        mStats.clearRemovedUserUidsLocked(userId);
-                    }
-                }, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
-            } catch (RejectedExecutionException e) {
-                return CompletableFuture.failedFuture(e);
+    public void scheduleCleanupDueToRemovedUser(int userId) {
+        // Initial quick clean-up after a user removal
+        mHandler.postDelayed(() -> {
+            synchronized (mStats) {
+                mStats.clearRemovedUserUidsLocked(userId);
             }
-        }
+        }, SYNC_USER_REMOVAL, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS);
+
+        // Final clean-up after a user removal, to take care of UIDs that were running
+        // longer than expected
+        mHandler.postDelayed(() -> {
+            synchronized (mStats) {
+                mStats.clearRemovedUserUidsLocked(userId);
+            }
+        }, SYNC_USER_REMOVAL, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS);
     }
 
     /**
@@ -368,42 +328,27 @@
      *         cancel it if needed
      */
     @GuardedBy("this")
-    private Future<?> scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable,
+    private void scheduleDelayedSyncLocked(int what, Runnable syncRunnable,
             long delayMillis) {
-        if (mExecutorService.isShutdown()) {
-            return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
-        }
-
-        if (lastScheduledSync != null) {
+        if (mHandler.hasMessages(what)) {
             // If there's already a scheduled task, leave it as is if we're trying to
             // re-schedule it again with a delay, otherwise cancel and re-schedule it.
             if (delayMillis == 0) {
-                lastScheduledSync.cancel(false);
+                mHandler.removeMessages(what);
             } else {
-                return lastScheduledSync;
+                return;
             }
         }
 
-        try {
-            return mExecutorService.schedule(syncRunnable, delayMillis, TimeUnit.MILLISECONDS);
-        } catch (RejectedExecutionException e) {
-            return CompletableFuture.failedFuture(e);
-        }
+        mHandler.postDelayed(syncRunnable, what, delayMillis);
     }
 
-    public synchronized Future<?> scheduleWrite() {
-        if (mExecutorService.isShutdown()) {
-            return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
-        }
-
+    /**
+     * Schedule and async writing of battery stats to disk
+     */
+    public synchronized void scheduleWrite() {
         scheduleSyncLocked("write", UPDATE_ALL);
-        // Since we use a single threaded executor, we can assume the next scheduled task's
-        // Future finishes after the sync.
-        try {
-            return mExecutorService.submit(mWriteTask);
-        } catch (RejectedExecutionException e) {
-            return CompletableFuture.failedFuture(e);
-        }
+        mHandler.post(mWriteTask);
     }
 
     /**
@@ -411,34 +356,25 @@
      * within the task, never wait on the resulting Future. This will result in a deadlock.
      */
     public synchronized void scheduleRunnable(Runnable runnable) {
-        try {
-            mExecutorService.submit(runnable);
-        } catch (RejectedExecutionException e) {
-            Slog.e(TAG, "Couldn't schedule " + runnable, e);
-        }
+        mHandler.post(runnable);
     }
 
     public void shutdown() {
-        mExecutorService.shutdownNow();
+        mHandler.removeMessages(SYNC_UPDATE);
+        mHandler.removeMessages(SYNC_WAKELOCK_CHANGE);
+        mHandler.removeMessages(SYNC_BATTERY_LEVEL_CHANGE);
+        mHandler.removeMessages(SYNC_PROCESS_STATE_CHANGE);
+        mHandler.removeMessages(SYNC_USER_REMOVAL);
     }
 
     @GuardedBy("this")
-    private Future<?> scheduleSyncLocked(String reason, int flags) {
-        if (mExecutorService.isShutdown()) {
-            return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
-        }
-
-        if (mCurrentFuture == null) {
+    private void scheduleSyncLocked(String reason, @ExternalUpdateFlag int flags) {
+        if (!mHandler.hasMessages(SYNC_UPDATE)) {
             mUpdateFlags = flags;
             mCurrentReason = reason;
-            try {
-                mCurrentFuture = mExecutorService.submit(mSyncTask);
-            } catch (RejectedExecutionException e) {
-                return CompletableFuture.failedFuture(e);
-            }
+            mHandler.postDelayed(mSyncTask, SYNC_UPDATE, 0);
         }
         mUpdateFlags |= flags;
-        return mCurrentFuture;
     }
 
     public long getLastCollectionTimeStamp() {
@@ -468,7 +404,6 @@
                 useLatestStates = mUseLatestStates;
                 mUpdateFlags = 0;
                 mCurrentReason = null;
-                mCurrentFuture = null;
                 mUseLatestStates = true;
                 if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
                     cancelSyncDueToBatteryLevelChangeLocked();
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 936fadf..e0ffd88 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -175,7 +175,6 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
@@ -948,19 +947,38 @@
         public @interface ExternalUpdateFlag {
         }
 
-        Future<?> scheduleSync(String reason, int flags);
-        Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+        /**
+         * Schedules a sync of kernel metrics in accordance with the specified flags.
+         */
+        void scheduleSync(String reason, @ExternalUpdateFlag int flags);
+
+        /**
+         * Schedules a CPU stats sync after a UID removal.
+         */
+        void scheduleCpuSyncDueToRemovedUid(int uid);
 
         /**
          * Schedule a sync because of a screen state change.
          */
-        Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+        void scheduleSyncDueToScreenStateChange(@ExternalUpdateFlag int flags, boolean onBattery,
                 boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates);
-        Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
+
+        /**
+         * Schedules a sync after a wakelock state change
+         */
+        void scheduleCpuSyncDueToWakelockChange(long delayMillis);
+
+        /**
+         * Canceles any pending sync due to a wakelock state change
+         */
         void cancelCpuSyncDueToWakelockChange();
-        Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
+
+        /**
+         * Schedules a sync caused by the battery level change
+         */
+        void scheduleSyncDueToBatteryLevelChange(long delayMillis);
         /** Schedule removal of UIDs corresponding to a removed user */
-        Future<?> scheduleCleanupDueToRemovedUser(int userId);
+        void scheduleCleanupDueToRemovedUser(int userId);
         /** Schedule a sync because of a process state change */
         void scheduleSyncDueToProcessStateChange(int flags, long delayMillis);
     }
@@ -12251,14 +12269,8 @@
             // start time
             long monotonicStartTime =
                     mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
-            mHandler.post(() -> {
-                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats);
-                try {
-                    batteryUsageStats.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "Cannot close BatteryUsageStats", e);
-                }
-            });
+            commitMonotonicClock();
+            mPowerStatsStore.storeBatteryUsageStatsAsync(monotonicStartTime, batteryUsageStats);
         }
     }
 
@@ -15379,6 +15391,10 @@
         mMaxLearnedBatteryCapacityUah = Math.max(mMaxLearnedBatteryCapacityUah, chargeFullUah);
 
         mBatteryTimeToFullSeconds = chargeTimeToFullSeconds;
+
+        if (mAccumulateBatteryUsageStats) {
+            mBatteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(this, mHandler);
+        }
     }
 
     public static boolean isOnBattery(int plugType, int status) {
@@ -17682,6 +17698,13 @@
         }
     }
 
+    /**
+     * Persists the monotonic clock associated with battery stats.
+     */
+    public void commitMonotonicClock() {
+        mMonotonicClock.write();
+    }
+
     @GuardedBy("this")
     public void prepareForDumpLocked() {
         // Need to retrieve current kernel wake lock stats before printing.
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index b466dd2..265f1dfc 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -23,17 +23,16 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Handler;
 import android.os.Process;
-import android.os.UidBatteryConsumer;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -49,20 +48,31 @@
     private final PowerStatsStore mPowerStatsStore;
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
+    private final int mAccumulatedBatteryUsageStatsSpanSize;
     private final Clock mClock;
     private final Object mLock = new Object();
     private List<PowerCalculator> mPowerCalculators;
     private UserPowerCalculator mUserPowerCalculator;
+    private long mLastAccumulationMonotonicHistorySize;
+
+    private static class AccumulatedBatteryUsageStats {
+        public BatteryUsageStats.Builder builder;
+        public long startWallClockTime;
+        public long startMonotonicTime;
+        public long endMonotonicTime;
+    }
 
     public BatteryUsageStatsProvider(@NonNull Context context,
             @NonNull PowerAttributor powerAttributor,
             @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
-            @NonNull PowerStatsStore powerStatsStore, @NonNull Clock clock) {
+            @NonNull PowerStatsStore powerStatsStore, int accumulatedBatteryUsageStatsSpanSize,
+            @NonNull Clock clock) {
         mContext = context;
         mPowerAttributor = powerAttributor;
         mPowerStatsStore = powerStatsStore;
         mPowerProfile = powerProfile;
         mCpuScalingPolicies = cpuScalingPolicies;
+        mAccumulatedBatteryUsageStatsSpanSize = accumulatedBatteryUsageStatsSpanSize;
         mClock = clock;
         mUserPowerCalculator = new UserPowerCalculator();
 
@@ -85,7 +95,10 @@
                     mPowerCalculators.add(
                             new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
                 }
-                mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_MEMORY)) {
+                    mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
+                }
                 if (!mPowerAttributor.isPowerComponentSupported(
                         BatteryConsumer.POWER_COMPONENT_WAKELOCK)) {
                     mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
@@ -141,7 +154,11 @@
                         BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) {
                     mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
                 }
-                mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+                // IDLE power attribution is covered by WakelockPowerStatsProcessor
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_WAKELOCK)) {
+                    mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+                }
                 if (!mPowerAttributor.isPowerComponentSupported(
                         BatteryConsumer.POWER_COMPONENT_ANY)) {
                     mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
@@ -159,53 +176,45 @@
     }
 
     /**
-     * Compute BatteryUsageStats for the period since the last accumulated stats were stored,
-     * add them to the accumulated stats and save the result.
+     * Conditionally runs a battery usage stats accumulation on the supplied handler.
+     */
+    public void accumulateBatteryUsageStatsAsync(BatteryStatsImpl stats, Handler handler) {
+        synchronized (this) {
+            long historySize = stats.getHistory().getMonotonicHistorySize();
+            if (historySize - mLastAccumulationMonotonicHistorySize
+                    < mAccumulatedBatteryUsageStatsSpanSize) {
+                return;
+            }
+            mLastAccumulationMonotonicHistorySize = historySize;
+        }
+
+        handler.post(() -> accumulateBatteryUsageStats(stats));
+    }
+
+    /**
+     * Computes BatteryUsageStats for the period since the last accumulated stats were stored,
+     * adds them to the accumulated stats and saves the result.
      */
     public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
-        BatteryUsageStats.Builder accumulatedBatteryUsageStatsBuilder = null;
+        AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
 
-        PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
-                AccumulatedBatteryUsageStatsSection.ID,
-                AccumulatedBatteryUsageStatsSection.TYPE);
-        if (powerStatsSpan != null) {
-            List<PowerStatsSpan.Section> sections = powerStatsSpan.getSections();
-            for (int i = sections.size() - 1; i >= 0; i--) {
-                PowerStatsSpan.Section section = sections.get(i);
-                if (AccumulatedBatteryUsageStatsSection.TYPE.equals(section.getType())) {
-                    accumulatedBatteryUsageStatsBuilder =
-                            ((AccumulatedBatteryUsageStatsSection) section)
-                                    .getBatteryUsageStatsBuilder();
-                    break;
-                }
-            }
-        }
+        final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+                .setMaxStatsAgeMs(0)
+                .includeProcessStateData()
+                .includePowerStateData()
+                .includeScreenStateData()
+                .build();
+        updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
 
-        // TODO(b/366493365): add the current batteryusagestats directly into the "accumulated"
-        // builder to avoid allocating a second CursorWindow
-        BatteryUsageStats.Builder currentBatteryUsageStatsBuilder =
-                getCurrentBatteryUsageStatsBuilder(stats,
-                        new BatteryUsageStatsQuery.Builder()
-                                .setMaxStatsAgeMs(0)
-                                .includeProcessStateData()
-                                .includePowerStateData()
-                                .includeScreenStateData()
-                                .build(),
-                        mClock.currentTimeMillis());
-
-        if (accumulatedBatteryUsageStatsBuilder == null) {
-            accumulatedBatteryUsageStatsBuilder = currentBatteryUsageStatsBuilder;
-        } else {
-            accumulatedBatteryUsageStatsBuilder.add(currentBatteryUsageStatsBuilder.build());
-            currentBatteryUsageStatsBuilder.discard();
-        }
-
-        powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
+        PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
         powerStatsSpan.addSection(
-                new AccumulatedBatteryUsageStatsSection(accumulatedBatteryUsageStatsBuilder));
-
+                new AccumulatedBatteryUsageStatsSection(accumulatedStats.builder));
+        powerStatsSpan.addTimeFrame(accumulatedStats.startMonotonicTime,
+                accumulatedStats.startWallClockTime,
+                accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
+        stats.commitMonotonicClock();
         mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
-                accumulatedBatteryUsageStatsBuilder::discard);
+                accumulatedStats.builder::discard);
     }
 
     /**
@@ -252,68 +261,73 @@
             BatteryUsageStatsQuery query, long currentTimeMs) {
         if ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
-            return getAccumulatedBatteryUsageStats(stats, query);
-        } else if (query.getToTimestamp() == 0) {
-            return getCurrentBatteryUsageStats(stats, query, currentTimeMs);
+            return getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+        } else if (query.getAggregatedToTimestamp() == 0) {
+            BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
+                    query.getMonotonicStartTime(),
+                    query.getMonotonicEndTime(), currentTimeMs);
+            return builder.build();
         } else {
             return getAggregatedBatteryUsageStats(stats, query);
         }
     }
 
     private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
-            BatteryUsageStatsQuery query) {
+            BatteryUsageStatsQuery query, long currentTimeMs) {
+        AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+        updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+        return accumulatedStats.builder.build();
+    }
+
+    private AccumulatedBatteryUsageStats loadAccumulatedBatteryUsageStats() {
+        AccumulatedBatteryUsageStats stats = new AccumulatedBatteryUsageStats();
+        stats.startWallClockTime = 0;
+        stats.startMonotonicTime = MonotonicClock.UNDEFINED;
+        stats.endMonotonicTime = MonotonicClock.UNDEFINED;
         PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
                 AccumulatedBatteryUsageStatsSection.ID,
                 AccumulatedBatteryUsageStatsSection.TYPE);
-
-        BatteryUsageStats.Builder accumulatedBatteryUsageStatsBuilder = null;
         if (powerStatsSpan != null) {
             List<PowerStatsSpan.Section> sections = powerStatsSpan.getSections();
-            if (sections.size() == 1) {
-                accumulatedBatteryUsageStatsBuilder =
-                        ((AccumulatedBatteryUsageStatsSection) sections.get(0))
-                                .getBatteryUsageStatsBuilder();
-            } else {
-                Slog.wtf(TAG, "Unexpected number of sections for type "
-                        + AccumulatedBatteryUsageStatsSection.TYPE);
+            for (int i = sections.size() - 1; i >= 0; i--) {
+                PowerStatsSpan.Section section = sections.get(i);
+                if (AccumulatedBatteryUsageStatsSection.TYPE.equals(section.getType())) {
+                    stats.builder = ((AccumulatedBatteryUsageStatsSection) section)
+                            .getBatteryUsageStatsBuilder();
+                    stats.startWallClockTime = powerStatsSpan.getMetadata().getStartTime();
+                    stats.startMonotonicTime = powerStatsSpan.getMetadata().getStartMonotonicTime();
+                    stats.endMonotonicTime = powerStatsSpan.getMetadata().getEndMonotonicTime();
+                    break;
+                }
             }
         }
+        return stats;
+    }
 
-        BatteryUsageStats currentBatteryUsageStats = getCurrentBatteryUsageStats(stats, query,
+    private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
+            BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
+        // TODO(b/366493365): add the current batteryusagestats directly into
+        //  `accumulatedStats.builder` to avoid allocating a second CursorWindow
+        BatteryUsageStats.Builder remainingBatteryUsageStats = computeBatteryUsageStats(stats,
+                query, accumulatedStats.endMonotonicTime, query.getMonotonicEndTime(),
                 mClock.currentTimeMillis());
 
-        BatteryUsageStats result;
-        if (accumulatedBatteryUsageStatsBuilder == null) {
-            result = currentBatteryUsageStats;
+        if (accumulatedStats.builder == null) {
+            accumulatedStats.builder = remainingBatteryUsageStats;
+            accumulatedStats.startWallClockTime = stats.getStartClockTime();
+            accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime();
+            accumulatedStats.endMonotonicTime = accumulatedStats.startMonotonicTime
+                    + accumulatedStats.builder.getStatsDuration();
         } else {
-            accumulatedBatteryUsageStatsBuilder.add(currentBatteryUsageStats);
-            try {
-                currentBatteryUsageStats.close();
-            } catch (IOException ex) {
-                Slog.e(TAG, "Closing BatteryUsageStats", ex);
-            }
-            result = accumulatedBatteryUsageStatsBuilder.build();
+            accumulatedStats.builder.add(remainingBatteryUsageStats.build());
+            accumulatedStats.endMonotonicTime += remainingBatteryUsageStats.getStatsDuration();
+            remainingBatteryUsageStats.discard();
         }
-
-        return result;
     }
 
-    private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats,
-            BatteryUsageStatsQuery query, long currentTimeMs) {
-        BatteryUsageStats.Builder builder = getCurrentBatteryUsageStatsBuilder(stats, query,
-                currentTimeMs);
-        BatteryUsageStats batteryUsageStats = builder.build();
-        if (batteryUsageStats.isProcessStateDataIncluded()) {
-            verify(batteryUsageStats);
-        }
-        return batteryUsageStats;
-    }
-
-    private BatteryUsageStats.Builder getCurrentBatteryUsageStatsBuilder(BatteryStatsImpl stats,
-            BatteryUsageStatsQuery query, long currentTimeMs) {
-        final long realtimeUs = mClock.elapsedRealtime() * 1000;
-        final long uptimeUs = mClock.uptimeMillis() * 1000;
-
+    private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats,
+            BatteryUsageStatsQuery query, long monotonicStartTime, long monotonicEndTime,
+            long currentTimeMs) {
         final boolean includePowerModels = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = ((query.getFlags()
@@ -324,11 +338,8 @@
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         String[] customEnergyConsumerNames;
-        long monotonicStartTime, monotonicEndTime;
         synchronized (stats) {
             customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
-            monotonicStartTime = stats.getMonotonicStartTime();
-            monotonicEndTime = stats.getMonotonicEndTime();
         }
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
@@ -337,12 +348,31 @@
                 query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
 
         synchronized (stats) {
-            // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
-            // of batteryUsageStats sessions to wall-clock adjustments
-            batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime());
-            batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs);
             final List<PowerCalculator> powerCalculators = getPowerCalculators();
             if (!powerCalculators.isEmpty()) {
+                if (monotonicStartTime != MonotonicClock.UNDEFINED
+                        || monotonicEndTime != MonotonicClock.UNDEFINED) {
+                    throw new IllegalStateException("BatteryUsageStatsQuery specifies a time "
+                            + "range that is incompatible with PowerCalculators: "
+                            + powerCalculators);
+                }
+            }
+
+            if (monotonicStartTime == MonotonicClock.UNDEFINED) {
+                monotonicStartTime = stats.getMonotonicStartTime();
+            }
+            batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()
+                    + (monotonicStartTime - stats.getMonotonicStartTime()));
+            if (monotonicEndTime != MonotonicClock.UNDEFINED) {
+                batteryUsageStatsBuilder.setStatsEndTimestamp(stats.getStartClockTime()
+                        + (monotonicEndTime - stats.getMonotonicStartTime()));
+            } else {
+                batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs);
+            }
+
+            if (!powerCalculators.isEmpty()) {
+                final long realtimeUs = mClock.elapsedRealtime() * 1000;
+                final long uptimeUs = mClock.uptimeMillis() * 1000;
                 final int[] powerComponents = query.getPowerComponents();
                 SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats();
                 for (int i = uidStats.size() - 1; i >= 0; i--) {
@@ -381,8 +411,7 @@
                 monotonicStartTime, monotonicEndTime);
 
         // Combine apps by the user if necessary
-        mUserPowerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs,
-                query);
+        mUserPowerCalculator.calculate(batteryUsageStatsBuilder, stats, 0, 0, query);
 
         populateGeneralInfo(batteryUsageStatsBuilder, stats);
         return batteryUsageStatsBuilder;
@@ -402,48 +431,6 @@
         }
     }
 
-    // STOPSHIP(b/229906525): remove verification before shipping
-    private static boolean sErrorReported;
-
-    private void verify(BatteryUsageStats stats) {
-        if (sErrorReported) {
-            return;
-        }
-
-        final double precision = 2.0;   // Allow rounding errors up to 2 mAh
-        final int[] components =
-                {BatteryConsumer.POWER_COMPONENT_CPU,
-                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        BatteryConsumer.POWER_COMPONENT_WIFI,
-                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
-        final int[] states =
-                {BatteryConsumer.PROCESS_STATE_FOREGROUND,
-                        BatteryConsumer.PROCESS_STATE_BACKGROUND,
-                        BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
-                        BatteryConsumer.PROCESS_STATE_CACHED};
-        for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
-            for (int component : components) {
-                double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
-                double sumStates = 0;
-                for (int state : states) {
-                    sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
-                }
-                if (sumStates > consumedPower + precision) {
-                    String error = "Sum of states exceeds total. UID = " + ubc.getUid() + " "
-                            + BatteryConsumer.powerComponentIdToString(component)
-                            + " total = " + consumedPower + " states = " + sumStates;
-                    if (!sErrorReported) {
-                        Slog.wtf(TAG, error);
-                        sErrorReported = true;
-                    } else {
-                        Slog.e(TAG, error);
-                    }
-                    return;
-                }
-            }
-        }
-    }
-
     private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats,
             BatteryUsageStatsQuery query) {
         final boolean includePowerModels = (query.getFlags()
@@ -489,8 +476,10 @@
             // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
             // while the "to" timestamp is *inclusive*.
             boolean isInRange =
-                    (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
-                            && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+                    (query.getAggregatedFromTimestamp() == 0
+                            || minTime > query.getAggregatedFromTimestamp())
+                            && (query.getAggregatedToTimestamp() == 0
+                            || maxTime <= query.getAggregatedToTimestamp());
             if (!isInRange) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index fc0611f..5105272 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -25,6 +25,7 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.MonotonicClock;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
@@ -147,6 +148,40 @@
             mTimeFrames.add(timeFrame);
         }
 
+        long getStartTime() {
+            long startTime = Long.MAX_VALUE;
+            for (int i = 0; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                if (timeFrame.startTime < startTime) {
+                    startTime = timeFrame.startTime;
+                }
+            }
+            return startTime != Long.MAX_VALUE ? startTime : 0;
+        }
+
+        long getStartMonotonicTime() {
+            long startTime = Long.MAX_VALUE;
+            for (int i = 0; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                if (timeFrame.startMonotonicTime < startTime) {
+                    startTime = timeFrame.startMonotonicTime;
+                }
+            }
+            return startTime != Long.MAX_VALUE ? startTime : MonotonicClock.UNDEFINED;
+        }
+
+        long getEndMonotonicTime() {
+            long maxTime = Long.MIN_VALUE;
+            for (int i = 0; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                long endTime = timeFrame.startMonotonicTime + timeFrame.duration;
+                if (endTime > maxTime) {
+                    maxTime = endTime;
+                }
+            }
+            return maxTime != Long.MIN_VALUE ? maxTime : MonotonicClock.UNDEFINED;
+        }
+
         void addSection(String sectionType) {
             // The number of sections per span is small, so there is no need to use a Set
             if (!mSections.contains(sectionType)) {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
index 5a6f973..3673617 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -149,7 +149,6 @@
      * Saves the specified span in the store.
      */
     public void storePowerStatsSpan(PowerStatsSpan span) {
-        maybeClearLegacyStore();
         lockStoreDirectory();
         try {
             if (!mStoreDir.exists()) {
@@ -203,13 +202,23 @@
      * Stores a {@link PowerStatsSpan} containing a single section for the supplied
      * battery usage stats.
      */
-    public void storeBatteryUsageStats(long monotonicStartTime,
+    public void storeBatteryUsageStatsAsync(long monotonicStartTime,
             BatteryUsageStats batteryUsageStats) {
-        PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
-        span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
-                batteryUsageStats.getStatsDuration());
-        span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
-        storePowerStatsSpan(span);
+        mHandler.post(() -> {
+            try {
+                PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+                span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+                        batteryUsageStats.getStatsDuration());
+                span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+                storePowerStatsSpan(span);
+            } finally {
+                try {
+                    batteryUsageStats.close();
+                } catch (IOException e) {
+                    Slog.e(TAG, "Cannot close BatteryUsageStats", e);
+                }
+            }
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
index 4a26d83..657701b 100644
--- a/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
@@ -21,7 +21,9 @@
 public class BinaryStatePowerStatsLayout extends EnergyConsumerPowerStatsLayout {
     public BinaryStatePowerStatsLayout() {
         addDeviceSectionUsageDuration();
+        addDeviceSectionPowerEstimate();
         addUidSectionUsageDuration();
+        addUidSectionPowerEstimate();
     }
 
     public BinaryStatePowerStatsLayout(PowerStats.Descriptor descriptor) {
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index c7ad564..c8170a1 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -117,6 +117,7 @@
                     PowerStatsSpan.Section section = sections.get(k);
                     populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
                             ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+                    // TODO(b/371614748): close the builder
                 }
             }
 
@@ -197,6 +198,7 @@
                 && powerState == BatteryConsumer.POWER_STATE_BATTERY;
 
         double[] totalPower = new double[1];
+        long[] durationMs = new long[1];
         MultiStateStats.States.forEachTrackedStateCombination(
                 powerComponentStats.getConfig().getDeviceStateConfig(),
                 states -> {
@@ -209,6 +211,7 @@
                     }
 
                     totalPower[0] += layout.getDevicePowerEstimate(deviceStats);
+                    durationMs[0] += layout.getUsageDuration(deviceStats);
 
                     if (hasBatteryLevelProperties) {
                         gatherBatteryLevelInfo(batteryLevelInfo, deviceStats);
@@ -223,9 +226,13 @@
         if (key != null) {
             deviceScope.addConsumedPower(key, totalPower[0],
                     BatteryConsumer.POWER_MODEL_UNDEFINED);
+            deviceScope.addUsageDurationMillis(key, durationMs[0]);
         }
-        deviceScope.addConsumedPower(powerComponentId, totalPower[0],
-                BatteryConsumer.POWER_MODEL_UNDEFINED);
+        key = deviceScope.getKey(powerComponentId, BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
+        if (key != null) {
+            deviceScope.addConsumedPower(key, totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
+            deviceScope.addUsageDurationMillis(key, durationMs[0]);
+        }
     }
 
     private void gatherBatteryLevelInfo(BatteryLevelInfo batteryLevelInfo, long[] deviceStats) {
@@ -373,9 +380,15 @@
 
             if (resultScreenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED
                     || resultPowerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
-                builder.addConsumedPower(powerComponentId,
-                        powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
-                        BatteryConsumer.POWER_MODEL_UNDEFINED);
+                BatteryConsumer.Key key = builder.getKey(powerComponentId,
+                        BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
+                if (key != null) {
+                    builder.addConsumedPower(key,
+                            powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
+                            BatteryConsumer.POWER_MODEL_UNDEFINED);
+                    builder.addUsageDurationMillis(key,
+                            durationByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
+                }
             }
             powerAllApps += powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED];
         }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 7b635d4..d7b60cf 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -39,7 +39,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.connectivity.WifiActivityEnergyInfo;
-import android.platform.test.ravenwood.RavenwoodRule;
+import android.platform.test.ravenwood.RavenwoodConfig;
 import android.power.PowerStatsInternal;
 import android.util.IntArray;
 import android.util.SparseArray;
@@ -52,7 +52,6 @@
 import com.android.internal.os.PowerProfile;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -67,25 +66,27 @@
 @SuppressWarnings("GuardedBy")
 @android.platform.test.annotations.DisabledOnRavenwood
 public class BatteryExternalStatsWorkerTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+    @RavenwoodConfig.Config
+    public final RavenwoodConfig mRavenwood = new RavenwoodConfig.Builder().build();
 
     private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
     private TestPowerStatsInternal mPowerStatsInternal;
+    private Handler mHandler;
 
     @Before
     public void setUp() {
         final Context context = InstrumentationRegistry.getContext();
 
+        mHandler = new Handler(Looper.getMainLooper());
         BatteryStatsImpl batteryStats = new BatteryStatsImpl(
                 new BatteryStatsImpl.BatteryStatsConfig.Builder().build(), Clock.SYSTEM_CLOCK,
                 new MonotonicClock(0, Clock.SYSTEM_CLOCK), null,
-                new Handler(Looper.getMainLooper()), null, null, null,
+                mHandler, null, null, null,
                 new PowerProfile(context, true /* forTest */), buildScalingPolicies(),
                 new PowerStatsUidResolver());
         mPowerStatsInternal = new TestPowerStatsInternal();
         mBatteryExternalStatsWorker =
-                new BatteryExternalStatsWorker(new TestInjector(context), batteryStats);
+                new BatteryExternalStatsWorker(new TestInjector(context), batteryStats, mHandler);
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index d36b553..d6f5036 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -38,7 +38,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Random;
-import java.util.concurrent.Future;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -324,9 +323,8 @@
         private boolean mSyncScheduled;
 
         @Override
-        public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
+        public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
             mSyncScheduled = true;
-            return null;
         }
 
         public boolean isSyncScheduled() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index e40a3e3..b67ec8b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -653,6 +653,44 @@
     }
 
     @Test
+    public void getMonotonicHistorySize() {
+        long lastHistorySize = mHistory.getMonotonicHistorySize();
+        mHistory.forceRecordAllHistory();
+
+        mClock.realtime = 1000;
+        mClock.uptime = 1000;
+        mHistory.recordEvent(mClock.realtime, mClock.uptime,
+                BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
+        long size = mHistory.getMonotonicHistorySize();
+        assertThat(size).isGreaterThan(lastHistorySize);
+        lastHistorySize = size;
+
+        mHistory.startNextFile(mClock.realtime);
+
+        size = mHistory.getMonotonicHistorySize();
+        assertThat(size).isEqualTo(lastHistorySize);
+
+        mClock.realtime = 2000;
+        mClock.uptime = 2000;
+        mHistory.recordEvent(mClock.realtime, mClock.uptime,
+                BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
+
+        size = mHistory.getMonotonicHistorySize();
+        assertThat(size).isGreaterThan(lastHistorySize);
+        lastHistorySize = size;
+
+        mHistory.startNextFile(mClock.realtime);
+
+        mClock.realtime = 3000;
+        mClock.uptime = 3000;
+        mHistory.recordEvent(mClock.realtime, mClock.uptime,
+                HistoryItem.EVENT_ALARM, "alarm", 42);
+
+        size = mHistory.getMonotonicHistorySize();
+        assertThat(size).isGreaterThan(lastHistorySize);
+    }
+
+    @Test
     public void testVarintParceler() {
         long[] values = {
                 0,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index c037f97..2f65a18 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -151,7 +151,7 @@
         }
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor,
-                mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore,
+                mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, 0,
                 mMockClock);
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 3931201..5912a99 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -134,7 +134,7 @@
 
     private int getNumberOfUidsInBatteryStats() throws Exception {
         ArraySet<Integer> uids = new ArraySet<>();
-        final String dumpsys = executeShellCommand("dumpsys batterystats --checkin");
+        final String dumpsys = executeShellCommand("dumpsys batterystats -c");
         for (String line : dumpsys.split("\n")) {
             final String[] parts = line.trim().split(",");
             if (parts.length < 5 ||
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index b30224b..e9d95fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -119,7 +120,7 @@
                 .isWithin(PRECISION).of(0.4);
 
         assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
-        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(180 * MINUTE_IN_MS);
     }
 
     @Test
@@ -142,7 +143,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -249,7 +250,8 @@
             }
         }
 
-        mStatsRule.setCurrentTime(54321);
+        setTime(180 * MINUTE_IN_MS);
+
         return batteryStats;
     }
 
@@ -266,7 +268,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 powerAttributor, mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
 
         return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
     }
@@ -296,7 +298,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -385,7 +387,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -474,7 +476,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
 
         batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
                 /* accumulateBatteryUsageStats */ false);
@@ -564,8 +566,19 @@
     }
 
     @Test
-    public void accumulateBatteryUsageStats() {
+    public void accumulateBatteryUsageStats() throws Throwable {
+        accumulateBatteryUsageStats(10000000, 1);
+        // Accumulate every 200 bytes of battery history
+        accumulateBatteryUsageStats(200, 2);
+        accumulateBatteryUsageStats(50, 5);
+        // Accumulate on every invocation of accumulateBatteryUsageStats
+        accumulateBatteryUsageStats(0, 7);
+    }
+
+    private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
+            int expectedNumberOfUpdates) throws Throwable {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        batteryStats.forceRecordAllHistory();
 
         setTime(5 * MINUTE_IN_MS);
 
@@ -574,69 +587,86 @@
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
 
-        PowerStatsStore powerStatsStore = new PowerStatsStore(
+        PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
                 new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
-                mStatsRule.getHandler());
+                mStatsRule.getHandler()));
         powerStatsStore.reset();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
-                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+        int[] count = new int[1];
+        doAnswer(inv -> {
+            count[0]++;
+            return null;
+        }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
 
-        batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
-                /* accumulateBatteryUsageStats */ true);
+        MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
+                powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
+                () -> 3500, new PowerStatsUidResolver());
+        for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+                powerComponentId++) {
+            powerAttributor.setPowerComponentSupported(powerComponentId, true);
+        }
+        powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
+
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                powerAttributor, mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore,
+                accumulatedBatteryUsageStatsSpanSize, mMockClock);
+
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
 
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
         }
+
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
         }
 
-        synchronized (batteryStats) {
-            batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-        }
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
 
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
         }
+
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
         }
         setTime(55 * MINUTE_IN_MS);
-        synchronized (batteryStats) {
-            batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-        }
+
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
 
         // This section has not been saved yet, but should be added to the accumulated totals
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         }
+
+        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
         }
         setTime(115 * MINUTE_IN_MS);
 
-        // Await completion
-        ConditionVariable done = new ConditionVariable();
-        mStatsRule.getHandler().post(done::open);
-        done.block();
+        // Pick up the remainder of battery history that has not yet been accumulated
+        provider.accumulateBatteryUsageStats(batteryStats);
+
+        mStatsRule.waitForBackgroundThread();
 
         BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
                 new BatteryUsageStatsQuery.Builder().accumulated().build());
-
         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
         assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
 
-        // Section 1 (saved): 20 - 10 = 10
-        // Section 2 (saved): 50 - 30 = 20
-        // Section 3 (fresh): 110 - 80 = 30
         // Total: 10 + 20 + 30 = 60
         assertThat(stats.getAggregateBatteryConsumer(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
@@ -657,6 +687,8 @@
         assertThat(uidBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isEqualTo(60 * MINUTE_IN_MS);
+
+        assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
     }
 
     private void setTime(long timeMs) {
@@ -682,7 +714,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
 
         PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
         doAnswer(invocation -> {
@@ -702,7 +734,7 @@
             assertThat(uid.getConsumedPower(componentId1))
                     .isWithin(PRECISION).of(8.33333);
             return null;
-        }).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+        }).when(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
 
         mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore,
                 /* accumulateBatteryUsageStats */ false);
@@ -714,7 +746,7 @@
 
         mStatsRule.waitForBackgroundThread();
 
-        verify(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+        verify(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
     }
 
     @Test
@@ -746,7 +778,7 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 2c03f9d..1e4454c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -43,7 +43,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Queue;
-import java.util.concurrent.Future;
 
 /**
  * Mocks a BatteryStatsImpl object.
@@ -288,30 +287,25 @@
         public int flags = 0;
 
         @Override
-        public Future<?> scheduleSync(String reason, int flags) {
-            return null;
+        public void scheduleSync(String reason, int flags) {
         }
 
         @Override
-        public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
-            return null;
+        public void scheduleCleanupDueToRemovedUser(int userId) {
         }
 
         @Override
-        public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
-            return null;
+        public void scheduleCpuSyncDueToRemovedUid(int uid) {
         }
 
         @Override
-        public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
+        public void scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
                 boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
             flags |= flag;
-            return null;
         }
 
         @Override
-        public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
-            return null;
+        public void scheduleCpuSyncDueToWakelockChange(long delayMillis) {
         }
 
         @Override
@@ -319,8 +313,7 @@
         }
 
         @Override
-        public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
-            return null;
+        public void scheduleSyncDueToBatteryLevelChange(long delayMillis) {
         }
 
         @Override
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
index dd5df76..8f33498 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
@@ -54,12 +54,14 @@
     private WakelockPowerStatsLayout mStatsLayout = new WakelockPowerStatsLayout();
 
     @Before
-    public void setup() {
-        mBatteryStats = new MockBatteryStatsImpl(mClock);
+    public void setup() throws Throwable {
+        mBatteryStats = mStatsRule.getBatteryStats();
         mBatteryStats.setPowerStatsCollectorEnabled(POWER_COMPONENT_WAKELOCK, true);
         mBatteryStats.getPowerStatsCollector(POWER_COMPONENT_WAKELOCK)
                 .addConsumer(ps -> mPowerStats = ps);
         mBatteryStats.onSystemReady(mock(Context.class));
+        // onSystemReady schedules the initial power stats collection. Wait for it to finish
+        mStatsRule.waitForBackgroundThread();
     }
 
     @Test
@@ -67,9 +69,6 @@
         PowerStatsCollector powerStatsCollector = mBatteryStats.getPowerStatsCollector(
                 POWER_COMPONENT_WAKELOCK);
 
-        // Establish a baseline
-        powerStatsCollector.collectAndDeliverStats();
-
         mBatteryStats.forceRecordAllHistory();
 
         mStatsRule.advanceSuspendedTime(1000);
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 467c15d..ea70287 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -55,6 +55,7 @@
         File systemDir = context.getCacheDir();
         Handler handler = new Handler(mBgThread.getLooper());
         mBatteryStatsService = new BatteryStatsService(context, systemDir);
+        mBatteryStatsService.setRailsStatsCollectionEnabled(false);
     }
 
     @After