Merge "Revert "Revert "Breaking history writing out of BatteryStatsImpl"""
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 09a52e4..da20626 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2326,11 +2326,6 @@
     public abstract void finishIteratingHistoryLocked();
 
     /**
-     * Return the base time offset for the battery history.
-     */
-    public abstract long getHistoryBaseTime();
-
-    /**
      * Returns the number of times the device has been started.
      */
     public abstract int getStartCount();
@@ -7615,8 +7610,6 @@
                 CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
                 getEndPlatformVersion());
 
-        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
-
         if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
             if (startIteratingHistoryLocked()) {
                 try {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 962870e..6909965 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -17,25 +17,35 @@
 package com.android.internal.os;
 
 import android.annotation.Nullable;
-import android.os.BatteryStats;
+import android.os.BatteryManager;
+import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.HistoryStepDetails;
+import android.os.BatteryStats.HistoryTag;
 import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Process;
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ParseUtils;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-import java.util.function.Supplier;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * BatteryStatsHistory encapsulates battery history files.
@@ -56,57 +66,62 @@
  * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
  * locks on BatteryStatsImpl object.
  */
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class BatteryStatsHistory {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    public static final int VERSION = 208;
+    private static final int VERSION = 208;
 
-    public static final String HISTORY_DIR = "battery-history";
-    public static final String FILE_SUFFIX = ".bin";
+    private static final String HISTORY_DIR = "battery-history";
+    private static final String FILE_SUFFIX = ".bin";
     private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
+
     // Part of initial delta int that specifies the time delta.
-    public static final int DELTA_TIME_MASK = 0x7ffff;
-    public static final int DELTA_TIME_LONG = 0x7ffff;   // The delta is a following long
-    public static final int DELTA_TIME_INT = 0x7fffe;    // The delta is a following int
-    public static final int DELTA_TIME_ABS = 0x7fffd;    // Following is an entire abs update.
+    static final int DELTA_TIME_MASK = 0x7ffff;
+    static final int DELTA_TIME_LONG = 0x7ffff;   // The delta is a following long
+    static final int DELTA_TIME_INT = 0x7fffe;    // The delta is a following int
+    static final int DELTA_TIME_ABS = 0x7fffd;    // Following is an entire abs update.
     // Flag in delta int: a new battery level int follows.
-    public static final int DELTA_BATTERY_LEVEL_FLAG  = 0x00080000;
+    static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
     // Flag in delta int: a new full state and battery status int follows.
-    public static final int DELTA_STATE_FLAG          = 0x00100000;
+    static final int DELTA_STATE_FLAG = 0x00100000;
     // Flag in delta int: a new full state2 int follows.
-    public static final int DELTA_STATE2_FLAG         = 0x00200000;
+    static final int DELTA_STATE2_FLAG = 0x00200000;
     // Flag in delta int: contains a wakelock or wakeReason tag.
-    public static final int DELTA_WAKELOCK_FLAG       = 0x00400000;
+    static final int DELTA_WAKELOCK_FLAG = 0x00400000;
     // Flag in delta int: contains an event description.
-    public static final int DELTA_EVENT_FLAG          = 0x00800000;
+    static final int DELTA_EVENT_FLAG = 0x00800000;
     // Flag in delta int: contains the battery charge count in uAh.
-    public static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
+    static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
     // These upper bits are the frequently changing state bits.
-    public static final int DELTA_STATE_MASK          = 0xfe000000;
+    static final int DELTA_STATE_MASK = 0xfe000000;
     // These are the pieces of battery state that are packed in to the upper bits of
     // the state int that have been packed in to the first delta int.  They must fit
     // in STATE_BATTERY_MASK.
-    public static final int STATE_BATTERY_MASK         = 0xff000000;
-    public static final int STATE_BATTERY_STATUS_MASK  = 0x00000007;
-    public static final int STATE_BATTERY_STATUS_SHIFT = 29;
-    public static final int STATE_BATTERY_HEALTH_MASK  = 0x00000007;
-    public static final int STATE_BATTERY_HEALTH_SHIFT = 26;
-    public static final int STATE_BATTERY_PLUG_MASK    = 0x00000003;
-    public static final int STATE_BATTERY_PLUG_SHIFT   = 24;
+    static final int STATE_BATTERY_MASK = 0xff000000;
+    static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+    static final int STATE_BATTERY_STATUS_SHIFT = 29;
+    static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+    static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+    static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+    static final int STATE_BATTERY_PLUG_SHIFT = 24;
     // We use the low bit of the battery state int to indicate that we have full details
     // from a battery level change.
-    public static final int BATTERY_DELTA_LEVEL_FLAG   = 0x00000001;
+    static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
     // Flag in history tag index: indicates that this is the first occurrence of this tag,
     // therefore the tag value is written in the parcel
-    public static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+    static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
 
-    @Nullable
-    private final Supplier<Integer> mMaxHistoryFiles;
     private final Parcel mHistoryBuffer;
+    private final File mSystemDir;
+    private final HistoryStepDetailsCalculator mStepDetailsCalculator;
     private final File mHistoryDir;
+    private final Clock mClock;
+
+    private int mMaxHistoryFiles;
+    private int mMaxHistoryBufferSize;
+
     /**
      * The active history file that the history buffer is backed up into.
      */
@@ -144,19 +159,77 @@
      */
     private int mParcelIndex = 0;
 
+    private final ReentrantLock mWriteLock = new ReentrantLock();
+
+    private final HistoryItem mHistoryCur = new HistoryItem();
+
+    private boolean mHaveBatteryLevel;
+    private boolean mRecordingHistory;
+
+    private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
+
+    private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+    private SparseArray<HistoryTag> mHistoryTags;
+    private final HistoryItem mHistoryLastWritten = new HistoryItem();
+    private final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+    private final HistoryItem mHistoryAddTmp = new HistoryItem();
+    private int mNextHistoryTagIdx = 0;
+    private int mNumHistoryTagChars = 0;
+    private int mHistoryBufferLastPos = -1;
+    private int mActiveHistoryStates = 0xffffffff;
+    private int mActiveHistoryStates2 = 0xffffffff;
+    private long mLastHistoryElapsedRealtimeMs = 0;
+    private long mTrackRunningHistoryElapsedRealtimeMs = 0;
+    private long mTrackRunningHistoryUptimeMs = 0;
+    private long mHistoryBaseTimeMs;
+
+    private byte mLastHistoryStepLevel = 0;
+
+    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+
+    /**
+     * A delegate responsible for computing additional details for a step in battery history.
+     */
+    public interface HistoryStepDetailsCalculator {
+        /**
+         * Returns additional details for the current history step or null.
+         */
+        @Nullable
+        HistoryStepDetails getHistoryStepDetails();
+
+        /**
+         * Resets the calculator to get ready for a new battery session
+         */
+        void clear();
+    }
+
     /**
      * Constructor
      *
-     * @param historyBuffer   The in-memory history buffer.
-     * @param systemDir       typically /data/system
-     * @param maxHistoryFiles the largest number of history buffer files to keep
+     * @param systemDir            typically /data/system
+     * @param maxHistoryFiles      the largest number of history buffer files to keep
+     * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
      */
-    public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
-            Supplier<Integer> maxHistoryFiles) {
-        mHistoryBuffer = historyBuffer;
-        mHistoryDir = new File(systemDir, HISTORY_DIR);
-        mMaxHistoryFiles = maxHistoryFiles;
+    public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+        this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
+                stepDetailsCalculator, clock);
+        initHistoryBuffer();
+    }
 
+    @VisibleForTesting
+    public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
+            int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+        mHistoryBuffer = historyBuffer;
+        mSystemDir = systemDir;
+        mMaxHistoryFiles = maxHistoryFiles;
+        mMaxHistoryBufferSize = maxHistoryBufferSize;
+        mStepDetailsCalculator = stepDetailsCalculator;
+        mClock = clock;
+
+        mHistoryDir = new File(systemDir, HISTORY_DIR);
         mHistoryDir.mkdirs();
         if (!mHistoryDir.exists()) {
             Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
@@ -192,19 +265,81 @@
         }
     }
 
-    /**
-     * Used when BatteryStatsImpl object is created from deserialization of a parcel,
-     * such as Settings app or checkin file.
-     * @param historyBuffer the history buffer
-     */
-    public BatteryStatsHistory(Parcel historyBuffer) {
+    public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+        mStepDetailsCalculator = stepDetailsCalculator;
+        mClock = clock;
+
+        mHistoryBuffer = Parcel.obtain();
+        mSystemDir = null;
         mHistoryDir = null;
-        mHistoryBuffer = historyBuffer;
-        mMaxHistoryFiles = null;
+        initHistoryBuffer();
     }
 
-    public File getHistoryDirectory() {
-        return mHistoryDir;
+    /**
+     * Used when BatteryStatsImpl object is created from deserialization of a parcel,
+     * such as a checkin file.
+     */
+    private BatteryStatsHistory(Parcel historyBuffer,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+        mHistoryBuffer = historyBuffer;
+        mClock = clock;
+        mSystemDir = null;
+        mHistoryDir = null;
+        mStepDetailsCalculator = stepDetailsCalculator;
+    }
+
+    private void initHistoryBuffer() {
+        mHistoryBaseTimeMs = 0;
+        mLastHistoryElapsedRealtimeMs = 0;
+        mTrackRunningHistoryElapsedRealtimeMs = 0;
+        mTrackRunningHistoryUptimeMs = 0;
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+        mHistoryLastLastWritten.clear();
+        mHistoryLastWritten.clear();
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
+        mHistoryBufferLastPos = -1;
+        mActiveHistoryStates = 0xffffffff;
+        mActiveHistoryStates2 = 0xffffffff;
+        if (mStepDetailsCalculator != null) {
+            mStepDetailsCalculator.clear();
+        }
+    }
+
+    /**
+     * Changes the maximum number of history files to be kept.
+     */
+    public void setMaxHistoryFiles(int maxHistoryFiles) {
+        mMaxHistoryFiles = maxHistoryFiles;
+    }
+
+    /**
+     * Changes the maximum size of the history buffer, in bytes.
+     */
+    public void setMaxHistoryBufferSize(int maxHistoryBufferSize) {
+        mMaxHistoryBufferSize = maxHistoryBufferSize;
+    }
+
+    /**
+     * Creates a read-only copy of the battery history.  Does not copy the files stored
+     * in the system directory, so it is not safe while actively writing history.
+     */
+    public BatteryStatsHistory copy() {
+        // Make a copy of battery history to avoid concurrent modification.
+        Parcel historyBuffer = Parcel.obtain();
+        historyBuffer.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+        return new BatteryStatsHistory(historyBuffer, mSystemDir, 0, 0, null, null);
+    }
+
+    /**
+     * Returns true if this instance only supports reading history.
+     */
+    public boolean isReadOnly() {
+        return mActiveFile == null;
     }
 
     /**
@@ -221,12 +356,13 @@
 
     /**
      * Create history AtomicFile from file number.
+     *
      * @param num file number.
      * @return AtomicFile object.
      */
     private AtomicFile getFile(int num) {
         return new AtomicFile(
-                new File(mHistoryDir,  num + FILE_SUFFIX));
+                new File(mHistoryDir, num + FILE_SUFFIX));
     }
 
     /**
@@ -234,7 +370,7 @@
      * create next history file.
      */
     public void startNextFile() {
-        if (mMaxHistoryFiles == null) {
+        if (mMaxHistoryFiles == 0) {
             Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
             return;
         }
@@ -264,7 +400,7 @@
         // if there are more history files than allowed, delete oldest history files.
         // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
         // config at run time.
-        while (mFileNumbers.size() > mMaxHistoryFiles.get()) {
+        while (mFileNumbers.size() > mMaxHistoryFiles) {
             int oldest = mFileNumbers.get(0);
             getFile(oldest).delete();
             mFileNumbers.remove(0);
@@ -272,36 +408,43 @@
     }
 
     /**
-     * Delete all existing history files. Active history file start from number 0 again.
+     * Clear history buffer and delete all existing history files. Active history file start from
+     * number 0 again.
      */
-    public void resetAllFiles() {
+    public void reset() {
+        if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
         for (Integer i : mFileNumbers) {
             getFile(i).delete();
         }
         mFileNumbers.clear();
         mFileNumbers.add(0);
         setActiveFile(0);
+
+        initHistoryBuffer();
     }
 
     /**
      * Start iterating history files and history buffer.
+     *
      * @return always return true.
      */
-    public boolean startIteratingHistory() {
+    public BatteryStatsHistoryIterator iterate() {
         mRecordCount = 0;
         mCurrentFileIndex = 0;
         mCurrentParcel = null;
         mCurrentParcelEnd = 0;
         mParcelIndex = 0;
-        return true;
+        mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
+        return mBatteryStatsHistoryIterator;
     }
 
     /**
      * Finish iterating history files and history buffer.
      */
-    public void finishIteratingHistory() {
+    void finishIteratingHistory() {
         // setDataPosition so mHistoryBuffer Parcel can be written.
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+        mBatteryStatsHistoryIterator = null;
         if (DEBUG) {
             Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
         }
@@ -311,11 +454,12 @@
      * When iterating history files and history buffer, always start from the lowest numbered
      * history file, when reached the mActiveFile (highest numbered history file), do not read from
      * mActiveFile, read from history buffer instead because the buffer has more updated data.
+     *
      * @param out a history item.
      * @return The parcel that has next record. null if finished all history files and history
-     *         buffer
+     * buffer
      */
-    public Parcel getNextParcel(BatteryStats.HistoryItem out) {
+    public Parcel getNextParcel(HistoryItem out) {
         if (mRecordCount == 0) {
             // reset out if it is the first record.
             out.clear();
@@ -323,8 +467,7 @@
         ++mRecordCount;
 
         // First iterate through all records in current parcel.
-        if (mCurrentParcel != null)
-        {
+        if (mCurrentParcel != null) {
             if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
                 // There are more records in current parcel.
                 return mCurrentParcel;
@@ -389,7 +532,8 @@
 
     /**
      * Read history file into a parcel.
-     * @param out the Parcel read into.
+     *
+     * @param out  the Parcel read into.
      * @param file the File to read from.
      * @return true if success, false otherwise.
      */
@@ -402,8 +546,8 @@
                 Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
                         + " duration ms:" + (SystemClock.uptimeMillis() - start));
             }
-        } catch(Exception e) {
-            Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
             return false;
         }
         out.unmarshall(raw, 0, raw.length);
@@ -413,6 +557,7 @@
 
     /**
      * Skip the header part of history parcel.
+     *
      * @param p history parcel to skip head.
      * @return true if version match, false if not.
      */
@@ -428,18 +573,68 @@
     }
 
     /**
+     * Writes the battery history contents for persistence.
+     */
+    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
+        out.writeBoolean(inclHistory);
+        if (inclHistory) {
+            writeToParcel(out);
+        }
+
+        out.writeInt(mHistoryTagPool.size());
+        for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+            HistoryTag tag = ent.getKey();
+            out.writeInt(ent.getValue());
+            out.writeString(tag.string);
+            out.writeInt(tag.uid);
+        }
+    }
+
+    /**
+     * Reads battery history contents from a persisted parcel.
+     */
+    public void readSummaryFromParcel(Parcel in) {
+        boolean inclHistory = in.readBoolean();
+        if (inclHistory) {
+            readFromParcel(in);
+        }
+
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
+
+        int numTags = in.readInt();
+        for (int i = 0; i < numTags; i++) {
+            int idx = in.readInt();
+            String str = in.readString();
+            int uid = in.readInt();
+            HistoryTag tag = new HistoryTag();
+            tag.string = str;
+            tag.uid = uid;
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(tag, idx);
+            if (idx >= mNextHistoryTagIdx) {
+                mNextHistoryTagIdx = idx + 1;
+            }
+            mNumHistoryTagChars += tag.string.length() + 1;
+        }
+    }
+
+    /**
      * Read all history files and serialize into a big Parcel.
      * Checkin file calls this method.
      *
      * @param out the output parcel
      */
     public void writeToParcel(Parcel out) {
+        writeHistoryBuffer(out);
         writeToParcel(out, false /* useBlobs */);
     }
 
     /**
      * This is for Settings app, when Settings app receives big history parcel, it call
      * this method to parse it into list of parcels.
+     *
      * @param out the output parcel
      */
     public void writeToBatteryUsageStatsParcel(Parcel out) {
@@ -450,13 +645,13 @@
     private void writeToParcel(Parcel out, boolean useBlobs) {
         final long start = SystemClock.uptimeMillis();
         out.writeInt(mFileNumbers.size() - 1);
-        for(int i = 0;  i < mFileNumbers.size() - 1; i++) {
+        for (int i = 0; i < mFileNumbers.size() - 1; i++) {
             AtomicFile file = getFile(mFileNumbers.get(i));
             byte[] raw = new byte[0];
             try {
                 raw = file.readFully();
-            } catch(Exception e) {
-                Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
             }
             if (useBlobs) {
                 out.writeBlob(raw);
@@ -480,17 +675,55 @@
         Parcel historyBuffer = Parcel.obtain();
         historyBuffer.unmarshall(historyBlob, 0, historyBlob.length);
 
-        BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer);
+        BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer, null,
+                Clock.SYSTEM_CLOCK);
         history.readFromParcel(in, true /* useBlobs */);
         return history;
     }
 
     /**
+     * Read history from a check-in file.
+     */
+    public boolean readSummary() {
+        if (mActiveFile == null) {
+            Slog.w(TAG, "readSummary: no history file associated with this instance");
+            return false;
+        }
+
+        Parcel parcel = Parcel.obtain();
+        try {
+            final long start = SystemClock.uptimeMillis();
+            if (mActiveFile.exists()) {
+                byte[] raw = mActiveFile.readFully();
+                if (raw.length > 0) {
+                    parcel.unmarshall(raw, 0, raw.length);
+                    parcel.setDataPosition(0);
+                    readHistoryBuffer(parcel);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "read history file::"
+                            + mActiveFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error reading battery history", e);
+            reset();
+            return false;
+        } finally {
+            parcel.recycle();
+        }
+        return true;
+    }
+
+    /**
      * This is for the check-in file, which has all history files embedded.
      *
      * @param in the input parcel.
      */
     public void readFromParcel(Parcel in) {
+        readHistoryBuffer(in);
         readFromParcel(in, false /* useBlobs */);
     }
 
@@ -498,7 +731,7 @@
         final long start = SystemClock.uptimeMillis();
         mHistoryParcels = new ArrayList<>();
         final int count = in.readInt();
-        for(int i = 0; i < count; i++) {
+        for (int i = 0; i < count; i++) {
             byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
             if (temp == null || temp.length == 0) {
                 continue;
@@ -521,10 +754,12 @@
         return stats.getAvailableBytes() > MIN_FREE_SPACE;
     }
 
+    @VisibleForTesting
     public List<Integer> getFilesNumbers() {
         return mFileNumbers;
     }
 
+    @VisibleForTesting
     public AtomicFile getActiveFile() {
         return mActiveFile;
     }
@@ -534,15 +769,972 @@
      */
     public int getHistoryUsedSize() {
         int ret = 0;
-        for(int i = 0; i < mFileNumbers.size() - 1; i++) {
+        for (int i = 0; i < mFileNumbers.size() - 1; i++) {
             ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
         }
         ret += mHistoryBuffer.dataSize();
         if (mHistoryParcels != null) {
-            for(int i = 0; i < mHistoryParcels.size(); i++) {
+            for (int i = 0; i < mHistoryParcels.size(); i++) {
                 ret += mHistoryParcels.get(i).dataSize();
             }
         }
         return ret;
     }
+
+    /**
+     * Enables/disables recording of history.  When disabled, all "record*" calls are a no-op.
+     */
+    public void setHistoryRecordingEnabled(boolean enabled) {
+        mRecordingHistory = enabled;
+    }
+
+    /**
+     * Returns true if history recording is enabled.
+     */
+    public boolean isRecordingHistory() {
+        return mRecordingHistory;
+    }
+
+    /**
+     * Forces history recording regardless of charging state.
+     */
+    @VisibleForTesting
+    public void forceRecordAllHistory() {
+        mHaveBatteryLevel = true;
+        mRecordingHistory = true;
+    }
+
+    /**
+     * Starts a history buffer by recording the current wall-clock time.
+     */
+    public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+            boolean reset) {
+        mRecordingHistory = true;
+        mHistoryCur.currentTime = mClock.currentTimeMillis();
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
+        mHistoryCur.currentTime = 0;
+    }
+
+    /**
+     * Prepares to continue recording after restoring previous history from persistent storage.
+     */
+    public void continueRecordingHistory() {
+        if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
+            return;
+        }
+
+        mRecordingHistory = true;
+        final long elapsedRealtimeMs = mClock.elapsedRealtime();
+        final long uptimeMs = mClock.uptimeMillis();
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
+        startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+    }
+
+    /**
+     * Notes the current battery state to be reflected in the next written history item.
+     */
+    public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
+        mHaveBatteryLevel = true;
+        setChargingState(charging);
+        mHistoryCur.batteryStatus = (byte) status;
+        mHistoryCur.batteryLevel = (byte) level;
+        mHistoryCur.batteryChargeUah = chargeUah;
+    }
+
+    /**
+     * Notes the current battery state to be reflected in the next written history item.
+     */
+    public void setBatteryState(int status, int level, int health, int plugType, int temperature,
+            int voltageMv, int chargeUah) {
+        mHaveBatteryLevel = true;
+        mHistoryCur.batteryStatus = (byte) status;
+        mHistoryCur.batteryLevel = (byte) level;
+        mHistoryCur.batteryHealth = (byte) health;
+        mHistoryCur.batteryPlugType = (byte) plugType;
+        mHistoryCur.batteryTemperature = (short) temperature;
+        mHistoryCur.batteryVoltage = (char) voltageMv;
+        mHistoryCur.batteryChargeUah = chargeUah;
+    }
+
+    /**
+     * Notes the current power plugged-in state to be reflected in the next written history item.
+     */
+    public void setPluggedInState(boolean pluggedIn) {
+        if (pluggedIn) {
+            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+        } else {
+            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+        }
+    }
+
+    /**
+     * Notes the current battery charging state to be reflected in the next written history item.
+     */
+    public void setChargingState(boolean charging) {
+        if (charging) {
+            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+        } else {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+        }
+    }
+
+    /**
+     * Records a history event with the given code, name and UID.
+     */
+    public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
+            int uid) {
+        mHistoryCur.eventCode = code;
+        mHistoryCur.eventTag = mHistoryCur.localEventTag;
+        mHistoryCur.eventTag.string = name;
+        mHistoryCur.eventTag.uid = uid;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a time change event.
+     */
+    public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+        if (!mRecordingHistory) {
+            return;
+        }
+
+        mHistoryCur.currentTime = currentTimeMs;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+                HistoryItem.CMD_CURRENT_TIME);
+        mHistoryCur.currentTime = 0;
+    }
+
+    /**
+     * Records a system shutdown event.
+     */
+    public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+        if (!mRecordingHistory) {
+            return;
+        }
+
+        mHistoryCur.currentTime = currentTimeMs;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
+        mHistoryCur.currentTime = 0;
+    }
+
+    /**
+     * Records a battery state change event.
+     */
+    public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
+            boolean isPlugged) {
+        mHistoryCur.batteryLevel = (byte) batteryLevel;
+        setPluggedInState(isPlugged);
+        if (DEBUG) {
+            Slog.v(TAG, "Battery unplugged to: "
+                    + Integer.toHexString(mHistoryCur.states));
+        }
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a history item with the amount of charge consumed by WiFi.  Used on certain devices
+     * equipped with on-device power metering.
+     */
+    public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
+            double monitoredRailChargeMah) {
+        mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a wakelock start event.
+     */
+    public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+            int uid) {
+        mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+        mHistoryCur.wakelockTag.string = historyName;
+        mHistoryCur.wakelockTag.uid = uid;
+        recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+    }
+
+    /**
+     * Updates the previous history event with a wakelock name and UID.
+     */
+    public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
+            int uid) {
+        if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
+            return false;
+        }
+        if (mHistoryLastWritten.wakelockTag != null) {
+            // We'll try to update the last tag.
+            mHistoryLastWritten.wakelockTag = null;
+            mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+            mHistoryCur.wakelockTag.string = historyName;
+            mHistoryCur.wakelockTag.uid = uid;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
+        return true;
+    }
+
+    /**
+     * Records an event when some state flag changes to true.
+     */
+    public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+        mHistoryCur.states |= stateFlags;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records an event when some state flag changes to false.
+     */
+    public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+        mHistoryCur.states &= ~stateFlags;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records an event when some state flags change to true and some to false.
+     */
+    public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
+            int stateStopFlags) {
+        mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records an event when some state2 flag changes to true.
+     */
+    public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+        mHistoryCur.states2 |= stateFlags;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records an event when some state2 flag changes to false.
+     */
+    public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+        mHistoryCur.states2 &= ~stateFlags;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records an wakeup event.
+     */
+    public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
+        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+        mHistoryCur.wakeReasonTag.string = reason;
+        mHistoryCur.wakeReasonTag.uid = 0;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a screen brightness change event.
+     */
+    public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
+            int brightnessBin) {
+        mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+                | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a GNSS signal level change event.
+     */
+    public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
+            int signalLevel) {
+        mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+                | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a device idle mode change event.
+     */
+    public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
+        mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+                | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a telephony state change event.
+     */
+    public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
+            int removeStateFlag, int state, int signalStrength) {
+        mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
+        if (state != -1) {
+            mHistoryCur.states =
+                    (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK)
+                            | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+        }
+        if (signalStrength != -1) {
+            mHistoryCur.states =
+                    (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+                            | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+        }
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a data connection type change event.
+     */
+    public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+            int dataConnectionType) {
+        mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK)
+                | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a WiFi supplicant state change event.
+     */
+    public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+            int supplState) {
+        mHistoryCur.states2 =
+                (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+                        | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Records a WiFi signal strength change event.
+     */
+    public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+            int strengthBin) {
+        mHistoryCur.states2 =
+                (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Writes the current history item to history.
+     */
+    public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
+        if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+            final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+            final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+            if (diffUptimeMs < (diffElapsedMs - 20)) {
+                final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+                mHistoryAddTmp.setTo(mHistoryLastWritten);
+                mHistoryAddTmp.wakelockTag = null;
+                mHistoryAddTmp.wakeReasonTag = null;
+                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+                writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+            }
+        }
+        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+        mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+        mTrackRunningHistoryUptimeMs = uptimeMs;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+    }
+
+    private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        if (!mHaveBatteryLevel || !mRecordingHistory) {
+            return;
+        }
+
+        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+        final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
+        final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
+        final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
+        final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
+        if (DEBUG) {
+            Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
+                    + Integer.toHexString(diffStates) + " lastDiff="
+                    + Integer.toHexString(lastDiffStates) + " diff2="
+                    + Integer.toHexString(diffStates2) + " lastDiff2="
+                    + Integer.toHexString(lastDiffStates2));
+        }
+        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+                && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
+                && (diffStates2 & lastDiffStates2) == 0
+                && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
+                && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+                && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+                && mHistoryLastWritten.stepDetails == null
+                && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+                || cur.eventCode == HistoryItem.EVENT_NONE)
+                && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+                && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+                && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+                && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+                && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+            // We can merge this new change in with the last one.  Merging is
+            // allowed as long as only the states have changed, and within those states
+            // 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);
+            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+            mHistoryBufferLastPos = -1;
+            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+            // If the last written history had a wakelock tag, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have a wakelock tag.
+            if (mHistoryLastWritten.wakelockTag != null) {
+                cur.wakelockTag = cur.localWakelockTag;
+                cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+            }
+            // If the last written history had a wake reason tag, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have a wakelock tag.
+            if (mHistoryLastWritten.wakeReasonTag != null) {
+                cur.wakeReasonTag = cur.localWakeReasonTag;
+                cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+            }
+            // If the last written history had an event, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have an event.
+            if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+                cur.eventCode = mHistoryLastWritten.eventCode;
+                cur.eventTag = cur.localEventTag;
+                cur.eventTag.setTo(mHistoryLastWritten.eventTag);
+            }
+            mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+        }
+        final int dataSize = mHistoryBuffer.dataSize();
+
+        if (dataSize >= mMaxHistoryBufferSize) {
+            if (mMaxHistoryBufferSize == 0) {
+                Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history");
+                mMaxHistoryBufferSize = 1024;
+            }
+
+            //open a new history file.
+            final long start = SystemClock.uptimeMillis();
+            writeHistory();
+            if (DEBUG) {
+                Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
+                        + (SystemClock.uptimeMillis() - start));
+            }
+            startNextFile();
+            mHistoryBuffer.setDataSize(0);
+            mHistoryBuffer.setDataPosition(0);
+            mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+            mHistoryBufferLastPos = -1;
+            mHistoryLastWritten.clear();
+            mHistoryLastLastWritten.clear();
+
+            // Mark every entry in the pool with a flag indicating that the tag
+            // has not yet been encountered while writing the current history buffer.
+            for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+                entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+            }
+            // Make a copy of mHistoryCur.
+            HistoryItem copy = new HistoryItem();
+            copy.setTo(cur);
+            // startRecordingHistory will reset mHistoryCur.
+            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+            // Add the copy into history buffer.
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
+            return;
+        }
+
+        if (dataSize == 0) {
+            // The history is currently empty; we need it to start with a time stamp.
+            cur.currentTime = mClock.currentTimeMillis();
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_RESET);
+        }
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
+    }
+
+    private void writeHistoryItem(long elapsedRealtimeMs,
+            @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
+        if (mBatteryStatsHistoryIterator != null) {
+            throw new IllegalStateException("Can't do this while iterating history!");
+        }
+        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
+        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
+        mHistoryLastWritten.states &= mActiveHistoryStates;
+        mHistoryLastWritten.states2 &= mActiveHistoryStates2;
+        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+        cur.wakelockTag = null;
+        cur.wakeReasonTag = null;
+        cur.eventCode = HistoryItem.EVENT_NONE;
+        cur.eventTag = null;
+        cur.tagsFirstOccurrence = false;
+        if (DEBUG) {
+            Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+                    + " now " + mHistoryBuffer.dataPosition()
+                    + " size is now " + mHistoryBuffer.dataSize());
+        }
+    }
+
+    /*
+        The history delta format uses flags to denote further data in subsequent ints in the parcel.
+
+        There is always the first token, which may contain the delta time, or an indicator of
+        the length of the time (int or long) following this token.
+
+        First token: always present,
+        31              23              15               7             0
+        â–ˆM|L|K|J|I|H|G|Fâ–ˆE|D|C|B|A|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆ
+
+        T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
+           follows containing the time, and 0x7ffff indicates a long immediately follows with the
+           delta time.
+        A: battery level changed and an int follows with battery data.
+        B: state changed and an int follows with state change data.
+        C: state2 has changed and an int follows with state2 change data.
+        D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
+        E: event data has changed and an event struct follows.
+        F: battery charge in coulombs has changed and an int with the charge follows.
+        G: state flag denoting that the mobile radio was active.
+        H: state flag denoting that the wifi radio was active.
+        I: state flag denoting that a wifi scan occurred.
+        J: state flag denoting that a wifi full lock was held.
+        K: state flag denoting that the gps was on.
+        L: state flag denoting that a wakelock was held.
+        M: state flag denoting that the cpu was running.
+
+        Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
+        with the time delta.
+
+        Battery level int: if A in the first token is set,
+        31              23              15               7             0
+        â–ˆL|L|L|L|L|L|L|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|V|V|V|V|V|V|Vâ–ˆV|V|V|V|V|V|V|Dâ–ˆ
+
+        D: indicates that extra history details follow.
+        V: the battery voltage.
+        T: the battery temperature.
+        L: the battery level (out of 100).
+
+        State change int: if B in the first token is set,
+        31              23              15               7             0
+        â–ˆS|S|S|H|H|H|P|Pâ–ˆF|E|D|C|B| | |Aâ–ˆ | | | | | | | â–ˆ | | | | | | | â–ˆ
+
+        A: wifi multicast was on.
+        B: battery was plugged in.
+        C: screen was on.
+        D: phone was scanning for signal.
+        E: audio was on.
+        F: a sensor was active.
+
+        State2 change int: if C in the first token is set,
+        31              23              15               7             0
+        â–ˆM|L|K|J|I|H|H|Gâ–ˆF|E|D|C| | | | â–ˆ | | | | | | | â–ˆ |B|B|B|A|A|A|Aâ–ˆ
+
+        A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
+        B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
+        C: a bluetooth scan was active.
+        D: the camera was active.
+        E: bluetooth was on.
+        F: a phone call was active.
+        G: the device was charging.
+        H: 2 bits indicating the device-idle (doze) state: off, light, full
+        I: the flashlight was on.
+        J: wifi was on.
+        K: wifi was running.
+        L: video was playing.
+        M: power save mode was on.
+
+        Wakelock/wakereason struct: if D in the first token is set,
+        Event struct: if E in the first token is set,
+        History step details struct: if D in the battery level int is set,
+
+        Battery charge int: if F in the first token is set, an int representing the battery charge
+        in coulombs follows.
+     */
+    /**
+     * Writes the delta between the previous and current history items into history buffer.
+     */
+    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+        if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+            dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
+            cur.writeToParcel(dest, 0);
+            return;
+        }
+
+        final long deltaTime = cur.time - last.time;
+        final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+        final int lastStateInt = buildStateInt(last);
+
+        int deltaTimeToken;
+        if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
+        } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
+            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
+        } else {
+            deltaTimeToken = (int) deltaTime;
+        }
+        int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
+        final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+                ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
+        mLastHistoryStepLevel = cur.batteryLevel;
+        final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
+        final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+        if (batteryLevelIntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
+        }
+        final int stateInt = buildStateInt(cur);
+        final boolean stateIntChanged = stateInt != lastStateInt;
+        if (stateIntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
+        }
+        final boolean state2IntChanged = cur.states2 != last.states2;
+        if (state2IntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
+        }
+        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+            firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
+        }
+        if (cur.eventCode != HistoryItem.EVENT_NONE) {
+            firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
+        }
+
+        final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
+        if (batteryChargeChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
+        }
+        dest.writeInt(firstToken);
+        if (DEBUG) {
+            Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                    + " deltaTime=" + deltaTime);
+        }
+
+        if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
+            if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime);
+                dest.writeInt((int) deltaTime);
+            } else {
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+                dest.writeLong(deltaTime);
+            }
+        }
+        if (batteryLevelIntChanged) {
+            dest.writeInt(batteryLevelInt);
+            if (DEBUG) {
+                Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+                        + Integer.toHexString(batteryLevelInt)
+                        + " batteryLevel=" + cur.batteryLevel
+                        + " batteryTemp=" + cur.batteryTemperature
+                        + " batteryVolt=" + (int) cur.batteryVoltage);
+            }
+        }
+        if (stateIntChanged) {
+            dest.writeInt(stateInt);
+            if (DEBUG) {
+                Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+                        + Integer.toHexString(stateInt)
+                        + " batteryStatus=" + cur.batteryStatus
+                        + " batteryHealth=" + cur.batteryHealth
+                        + " batteryPlugType=" + cur.batteryPlugType
+                        + " states=0x" + Integer.toHexString(cur.states));
+            }
+        }
+        if (state2IntChanged) {
+            dest.writeInt(cur.states2);
+            if (DEBUG) {
+                Slog.i(TAG, "WRITE DELTA: states2=0x"
+                        + Integer.toHexString(cur.states2));
+            }
+        }
+        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+            int wakeLockIndex;
+            int wakeReasonIndex;
+            if (cur.wakelockTag != null) {
+                wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+                if (DEBUG) {
+                    Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+                            + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+                }
+            } else {
+                wakeLockIndex = 0xffff;
+            }
+            if (cur.wakeReasonTag != null) {
+                wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+                if (DEBUG) {
+                    Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+                            + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+                }
+            } else {
+                wakeReasonIndex = 0xffff;
+            }
+            dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex);
+            if (cur.wakelockTag != null
+                    && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakelockTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+            if (cur.wakeReasonTag != null
+                    && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakeReasonTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+        }
+        if (cur.eventCode != HistoryItem.EVENT_NONE) {
+            final int index = writeHistoryTag(cur.eventTag);
+            final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
+            dest.writeInt(codeAndIndex);
+            if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.eventTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+                        + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+                        + cur.eventTag.string);
+            }
+        }
+
+        cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails();
+        if (includeStepDetails != 0) {
+            cur.stepDetails.writeToParcel(dest);
+        }
+
+        if (batteryChargeChanged) {
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
+            dest.writeInt(cur.batteryChargeUah);
+        }
+        dest.writeDouble(cur.modemRailChargeMah);
+        dest.writeDouble(cur.wifiRailChargeMah);
+    }
+
+    private int buildBatteryLevelInt(HistoryItem h) {
+        return ((((int) h.batteryLevel) << 25) & 0xfe000000)
+                | ((((int) h.batteryTemperature) << 15) & 0x01ff8000)
+                | ((((int) h.batteryVoltage) << 1) & 0x00007ffe);
+    }
+
+    private int buildStateInt(HistoryItem h) {
+        int plugType = 0;
+        if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+            plugType = 1;
+        } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+            plugType = 2;
+        } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+            plugType = 3;
+        }
+        return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+                | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+                | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+                | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+    }
+
+    /**
+     * Returns the index for the specified tag. If this is the first time the tag is encountered
+     * while writing the current history buffer, the method returns
+     * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+     */
+    private int writeHistoryTag(HistoryTag tag) {
+        if (tag.string == null) {
+            Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+        }
+
+        final int stringLength = tag.string.length();
+        if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+            Slog.e(TAG, "Long battery history tag: " + tag.string);
+            tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+        }
+
+        Integer idxObj = mHistoryTagPool.get(tag);
+        int idx;
+        if (idxObj != null) {
+            idx = idxObj;
+            if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+            }
+            return idx;
+        } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
+            idx = mNextHistoryTagIdx;
+            HistoryTag key = new HistoryTag();
+            key.setTo(tag);
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(key, idx);
+            mNextHistoryTagIdx++;
+
+            mNumHistoryTagChars += stringLength + 1;
+            if (mHistoryTags != null) {
+                mHistoryTags.put(idx, key);
+            }
+            return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+        } else {
+            // Tag pool overflow: include the tag itself in the parcel
+            return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+        }
+    }
+
+    /**
+     * Don't allow any more batching in to the current history event.
+     */
+    public void commitCurrentHistoryBatchLocked() {
+        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+    }
+
+    /**
+     * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
+     */
+    public void writeHistory() {
+        if (mActiveFile == null) {
+            Slog.w(TAG, "writeHistory: no history file associated with this instance");
+            return;
+        }
+
+        Parcel p = Parcel.obtain();
+        try {
+            final long start = SystemClock.uptimeMillis();
+            writeHistoryBuffer(p);
+            if (DEBUG) {
+                Slog.d(TAG, "writeHistoryBuffer duration ms:"
+                        + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+            }
+            writeParcelToFileLocked(p, mActiveFile);
+        } finally {
+            p.recycle();
+        }
+    }
+
+    /**
+     * Reads history buffer from a persisted Parcel.
+     */
+    public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
+        final int version = in.readInt();
+        if (version != BatteryStatsHistory.VERSION) {
+            Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+                    + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+            return;
+        }
+
+        final long historyBaseTime = in.readLong();
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+
+        int bufSize = in.readInt();
+        int curPos = in.dataPosition();
+        if (bufSize >= (mMaxHistoryBufferSize * 100)) {
+            throw new ParcelFormatException(
+                    "File corrupt: history data buffer too large " + bufSize);
+        } else if ((bufSize & ~3) != bufSize) {
+            throw new ParcelFormatException(
+                    "File corrupt: history data buffer not aligned " + bufSize);
+        } else {
+            if (DEBUG) {
+                Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+                        + " bytes at " + curPos);
+            }
+            mHistoryBuffer.appendFrom(in, curPos, bufSize);
+            in.setDataPosition(curPos + bufSize);
+        }
+
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** OLD mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        mHistoryBaseTimeMs = historyBaseTime;
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** NEW mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+
+        // We are just arbitrarily going to insert 1 minute from the sample of
+        // the last run until samples in this run.
+        if (mHistoryBaseTimeMs > 0) {
+            long oldnow = mClock.elapsedRealtime();
+            mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
+            if (DEBUG) {
+                StringBuilder sb = new StringBuilder(128);
+                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
+                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+                Slog.i(TAG, sb.toString());
+            }
+        }
+    }
+
+    private void writeHistoryBuffer(Parcel out) {
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            sb.append(" mLastHistoryElapsedRealtimeMs: ");
+            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        out.writeInt(BatteryStatsHistory.VERSION);
+        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+        out.writeInt(mHistoryBuffer.dataSize());
+        if (DEBUG) {
+            Slog.i(TAG, "***************** WRITING HISTORY: "
+                    + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+        }
+        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+    }
+
+    private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
+        FileOutputStream fos = null;
+        mWriteLock.lock();
+        try {
+            final long startTimeMs = SystemClock.uptimeMillis();
+            fos = file.startWrite();
+            fos.write(p.marshall());
+            fos.flush();
+            file.finishWrite(fos);
+            if (DEBUG) {
+                Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath()
+                        + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
+                        + " bytes:" + p.dataSize());
+            }
+            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+                    "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+        } catch (IOException e) {
+            Slog.w(TAG, "Error writing battery statistics", e);
+            file.failWrite(fos);
+        } finally {
+            mWriteLock.unlock();
+        }
+    }
+
+    /**
+     * Returns the total number of history tags in the tag pool.
+     */
+    public int getHistoryStringPoolSize() {
+        return mHistoryTagPool.size();
+    }
+
+    /**
+     * Returns the total number of bytes occupied by the history tag pool.
+     */
+    public int getHistoryStringPoolBytes() {
+        return mNumHistoryTagChars;
+    }
+
+    /**
+     * Returns the string held by the requested history tag.
+     */
+    public String getHistoryTagPoolString(int index) {
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.string : null;
+    }
+
+    /**
+     * Returns the UID held by the requested history tag.
+     */
+    public int getHistoryTagPoolUid(int index) {
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+    }
+
+    private void ensureHistoryTagArray() {
+        if (mHistoryTags != null) {
+            return;
+        }
+
+        mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+        for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+            mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
+                    entry.getKey());
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index de8b414..1bf878cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -36,7 +36,6 @@
 
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
-        mBatteryStatsHistory.startIteratingHistory();
     }
 
     /**
@@ -231,4 +230,11 @@
         out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
         out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
     }
+
+    /**
+     * Should be called when iteration is complete.
+     */
+    public void close() {
+        mBatteryStatsHistory.finishIteratingHistory();
+    }
 }
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 fe4aa53..df902c2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -657,7 +657,7 @@
 
         // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
-            mStats.addHistoryEventLocked(
+            mStats.recordHistoryEventLocked(
                     elapsedRealtime,
                     uptime,
                     BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
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 0c9ada8..968f916 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -108,6 +108,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator;
 import com.android.internal.os.BatteryStatsHistoryIterator;
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderTransactionNameResolver;
@@ -173,7 +174,6 @@
     private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
     private static final boolean DEBUG_BINDER_STATS = false;
     private static final boolean DEBUG_MEMORY = false;
-    private static final boolean DEBUG_HISTORY = false;
 
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
@@ -322,6 +322,11 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
 
+    @NonNull
+    BatteryStatsHistory copyHistory() {
+        return mHistory.copy();
+    }
+
     @VisibleForTesting
     public final class UidToRemove {
         private final int mStartUid;
@@ -413,7 +418,7 @@
                 if (changed) {
                     final long uptimeMs = mClock.uptimeMillis();
                     final long elapsedRealtimeMs = mClock.elapsedRealtime();
-                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                    mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
                 }
             }
         }
@@ -668,16 +673,16 @@
     /**
      * Mapping isolated uids to the actual owning app uid.
      */
-    final SparseIntArray mIsolatedUids = new SparseIntArray();
+    private final SparseIntArray mIsolatedUids = new SparseIntArray();
     /**
      * Internal reference count of isolated uids.
      */
-    final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+    private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
 
     /**
      * The statistics we have collected organized by uids.
      */
-    final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
+    private final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
 
     // A set of pools of currently active timers.  When a timer is queried, we will divide the
     // elapsed time by the number of active timers to arrive at that timer's share of the time.
@@ -685,20 +690,21 @@
     // changes.
     @VisibleForTesting
     protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
-    final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
-    final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
-    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
-    final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
-    final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
+    private final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+    private final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+    private final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
+            new SparseArray<>();
+    private final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
+    private final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
 
     // Last partial timers we use for distributing CPU usage.
     @VisibleForTesting
@@ -713,69 +719,24 @@
     protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(true);
 
     private boolean mSystemReady;
-    boolean mShuttingDown;
+    private boolean mShuttingDown;
 
-    final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+    private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+    private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
+            new HistoryStepDetailsCalculatorImpl();
 
-    long mHistoryBaseTimeMs;
-    protected boolean mHaveBatteryLevel = false;
-    protected boolean mRecordingHistory = false;
-    int mNumHistoryItems;
-
-    private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
-    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
-
-    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
-    private SparseArray<HistoryTag> mHistoryTags;
-    final Parcel mHistoryBuffer = Parcel.obtain();
-    final HistoryItem mHistoryLastWritten = new HistoryItem();
-    final HistoryItem mHistoryLastLastWritten = new HistoryItem();
-    final HistoryItem mHistoryAddTmp = new HistoryItem();
-    int mNextHistoryTagIdx = 0;
-    int mNumHistoryTagChars = 0;
-    int mHistoryBufferLastPos = -1;
-    int mActiveHistoryStates = 0xffffffff;
-    int mActiveHistoryStates2 = 0xffffffff;
-    long mLastHistoryElapsedRealtimeMs = 0;
-    long mTrackRunningHistoryElapsedRealtimeMs = 0;
-    long mTrackRunningHistoryUptimeMs = 0;
+    private boolean mHaveBatteryLevel = false;
+    private boolean mBatteryPluggedIn;
+    private int mBatteryStatus;
+    private int mBatteryLevel;
+    private int mBatteryPlugType;
+    private int mBatteryChargeUah;
+    private int mBatteryHealth;
+    private int mBatteryTemperature;
+    private int mBatteryVoltageMv = -1;
 
     @NonNull
-    final BatteryStatsHistory mBatteryStatsHistory;
-
-    final HistoryItem mHistoryCur = new HistoryItem();
-
-    // Used by computeHistoryStepDetails
-    HistoryStepDetails mLastHistoryStepDetails = null;
-    byte mLastHistoryStepLevel = 0;
-    final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
-    final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
-
-    /**
-     * Total time (in milliseconds) spent executing in user code.
-     */
-    long mLastStepCpuUserTimeMs;
-    long mCurStepCpuUserTimeMs;
-    /**
-     * Total time (in milliseconds) spent executing in kernel code.
-     */
-    long mLastStepCpuSystemTimeMs;
-    long mCurStepCpuSystemTimeMs;
-    /**
-     * Times from /proc/stat (but measured in milliseconds).
-     */
-    long mLastStepStatUserTimeMs;
-    long mLastStepStatSystemTimeMs;
-    long mLastStepStatIOWaitTimeMs;
-    long mLastStepStatIrqTimeMs;
-    long mLastStepStatSoftIrqTimeMs;
-    long mLastStepStatIdleTimeMs;
-    long mCurStepStatUserTimeMs;
-    long mCurStepStatSystemTimeMs;
-    long mCurStepStatIOWaitTimeMs;
-    long mCurStepStatIrqTimeMs;
-    long mCurStepStatSoftIrqTimeMs;
-    long mCurStepStatIdleTimeMs;
+    private final BatteryStatsHistory mHistory;
 
     private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
 
@@ -1391,7 +1352,6 @@
     int mDischargeUnplugLevel;
     int mDischargePlugLevel;
     int mDischargeCurrentLevel;
-    int mCurrentBatteryLevel;
     int mLowDischargeAmountSinceCharge;
     int mHighDischargeAmountSinceCharge;
     int mDischargeScreenOnUnplugLevel;
@@ -1443,7 +1403,6 @@
 
     private int mNumConnectivityChange;
 
-    private int mBatteryVoltageMv = -1;
     private int mEstimatedBatteryCapacityMah = -1;
 
     private int mLastLearnedBatteryCapacityUah = -1;
@@ -1627,28 +1586,27 @@
     }
 
     public BatteryStatsImpl(Clock clock) {
-        this(clock, (File) null);
+        this(clock, null);
     }
 
     public BatteryStatsImpl(Clock clock, File historyDirectory) {
         init(clock);
+        mHandler = null;
+        mConstants = new Constants(mHandler);
         mStartClockTimeMs = clock.currentTimeMillis();
         mCheckinFile = null;
         mDailyFile = null;
         if (historyDirectory == null) {
             mStatsFile = null;
-            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
         } else {
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
-            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, historyDirectory,
-                    this::getMaxHistoryFiles);
+            mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         }
-        mHandler = null;
         mPlatformIdleStateCallback = null;
         mMeasuredEnergyRetriever = null;
         mUserInfoProvider = null;
-        mConstants = new Constants(mHandler);
-        clearHistoryLocked();
     }
 
     private void init(Clock clock) {
@@ -3911,406 +3869,188 @@
         return kmt;
     }
 
-    /**
-     * Returns the index for the specified tag. If this is the first time the tag is encountered
-     * while writing the current history buffer, the method returns
-     * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
-     */
-    private int writeHistoryTag(HistoryTag tag) {
-        if (tag.string == null) {
-            Slog.wtfStack(TAG, "writeHistoryTag called with null name");
-        }
+    private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator {
+        private final HistoryStepDetails mDetails = new HistoryStepDetails();
 
-        final int stringLength = tag.string.length();
-        if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
-            Slog.e(TAG, "Long battery history tag: " + tag.string);
-            tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
-        }
+        private boolean mHasHistoryStepDetails;
 
-        Integer idxObj = mHistoryTagPool.get(tag);
-        int idx;
-        if (idxObj != null) {
-            idx = idxObj;
-            if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+        private int mLastHistoryStepLevel;
+
+        /**
+         * Total time (in milliseconds) spent executing in user code.
+         */
+        private long mLastStepCpuUserTimeMs;
+        private long mCurStepCpuUserTimeMs;
+        /**
+         * Total time (in milliseconds) spent executing in kernel code.
+         */
+        private long mLastStepCpuSystemTimeMs;
+        private long mCurStepCpuSystemTimeMs;
+        /**
+         * Times from /proc/stat (but measured in milliseconds).
+         */
+        private long mLastStepStatUserTimeMs;
+        private long mLastStepStatSystemTimeMs;
+        private long mLastStepStatIOWaitTimeMs;
+        private long mLastStepStatIrqTimeMs;
+        private long mLastStepStatSoftIrqTimeMs;
+        private long mLastStepStatIdleTimeMs;
+        private long mCurStepStatUserTimeMs;
+        private long mCurStepStatSystemTimeMs;
+        private long mCurStepStatIOWaitTimeMs;
+        private long mCurStepStatIrqTimeMs;
+        private long mCurStepStatSoftIrqTimeMs;
+        private long mCurStepStatIdleTimeMs;
+
+        @Override
+        public HistoryStepDetails getHistoryStepDetails() {
+            if (mBatteryLevel >= mLastHistoryStepLevel && mHasHistoryStepDetails) {
+                mLastHistoryStepLevel = mBatteryLevel;
+                return null;
             }
-            return idx;
-        } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
-            idx = mNextHistoryTagIdx;
-            HistoryTag key = new HistoryTag();
-            key.setTo(tag);
-            tag.poolIdx = idx;
-            mHistoryTagPool.put(key, idx);
-            mNextHistoryTagIdx++;
 
-            mNumHistoryTagChars += stringLength + 1;
-            if (mHistoryTags != null) {
-                mHistoryTags.put(idx, key);
-            }
-            return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
-        } else {
-            // Tag pool overflow: include the tag itself in the parcel
-            return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
-        }
-    }
+            // Perform a CPU update right after we do this collection, so we have started
+            // collecting good data for the next step.
+            requestImmediateCpuUpdate();
 
-    /*
-        The history delta format uses flags to denote further data in subsequent ints in the parcel.
-
-        There is always the first token, which may contain the delta time, or an indicator of
-        the length of the time (int or long) following this token.
-
-        First token: always present,
-        31              23              15               7             0
-        â–ˆM|L|K|J|I|H|G|Fâ–ˆE|D|C|B|A|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆ
-
-        T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
-           follows containing the time, and 0x7ffff indicates a long immediately follows with the
-           delta time.
-        A: battery level changed and an int follows with battery data.
-        B: state changed and an int follows with state change data.
-        C: state2 has changed and an int follows with state2 change data.
-        D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
-        E: event data has changed and an event struct follows.
-        F: battery charge in coulombs has changed and an int with the charge follows.
-        G: state flag denoting that the mobile radio was active.
-        H: state flag denoting that the wifi radio was active.
-        I: state flag denoting that a wifi scan occurred.
-        J: state flag denoting that a wifi full lock was held.
-        K: state flag denoting that the gps was on.
-        L: state flag denoting that a wakelock was held.
-        M: state flag denoting that the cpu was running.
-
-        Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
-        with the time delta.
-
-        Battery level int: if A in the first token is set,
-        31              23              15               7             0
-        â–ˆL|L|L|L|L|L|L|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|V|V|V|V|V|V|Vâ–ˆV|V|V|V|V|V|V|Dâ–ˆ
-
-        D: indicates that extra history details follow.
-        V: the battery voltage.
-        T: the battery temperature.
-        L: the battery level (out of 100).
-
-        State change int: if B in the first token is set,
-        31              23              15               7             0
-        â–ˆS|S|S|H|H|H|P|Pâ–ˆF|E|D|C|B| | |Aâ–ˆ | | | | | | | â–ˆ | | | | | | | â–ˆ
-
-        A: wifi multicast was on.
-        B: battery was plugged in.
-        C: screen was on.
-        D: phone was scanning for signal.
-        E: audio was on.
-        F: a sensor was active.
-
-        State2 change int: if C in the first token is set,
-        31              23              15               7             0
-        â–ˆM|L|K|J|I|H|H|Gâ–ˆF|E|D|C| | | | â–ˆ | | | | | | | â–ˆ |B|B|B|A|A|A|Aâ–ˆ
-
-        A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
-        B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
-        C: a bluetooth scan was active.
-        D: the camera was active.
-        E: bluetooth was on.
-        F: a phone call was active.
-        G: the device was charging.
-        H: 2 bits indicating the device-idle (doze) state: off, light, full
-        I: the flashlight was on.
-        J: wifi was on.
-        K: wifi was running.
-        L: video was playing.
-        M: power save mode was on.
-
-        Wakelock/wakereason struct: if D in the first token is set,
-        TODO(adamlesinski): describe wakelock/wakereason struct.
-
-        Event struct: if E in the first token is set,
-        TODO(adamlesinski): describe the event struct.
-
-        History step details struct: if D in the battery level int is set,
-        TODO(adamlesinski): describe the history step details struct.
-
-        Battery charge int: if F in the first token is set, an int representing the battery charge
-        in coulombs follows.
-     */
-
-    @GuardedBy("this")
-    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
-        if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
-            dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
-            cur.writeToParcel(dest, 0);
-            return;
-        }
-
-        final long deltaTime = cur.time - last.time;
-        final int lastBatteryLevelInt = buildBatteryLevelInt(last);
-        final int lastStateInt = buildStateInt(last);
-
-        int deltaTimeToken;
-        if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
-            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
-        } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
-            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
-        } else {
-            deltaTimeToken = (int)deltaTime;
-        }
-        int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
-        final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
-                ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
-        final boolean computeStepDetails = includeStepDetails != 0
-                || mLastHistoryStepDetails == null;
-        final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
-        final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
-        if (batteryLevelIntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
-        }
-        final int stateInt = buildStateInt(cur);
-        final boolean stateIntChanged = stateInt != lastStateInt;
-        if (stateIntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
-        }
-        final boolean state2IntChanged = cur.states2 != last.states2;
-        if (state2IntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
-        }
-        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
-            firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
-        }
-        if (cur.eventCode != HistoryItem.EVENT_NONE) {
-            firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
-        }
-
-        final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
-        if (batteryChargeChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
-        }
-        dest.writeInt(firstToken);
-        if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
-                + " deltaTime=" + deltaTime);
-
-        if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
-            if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
-                dest.writeInt((int)deltaTime);
-            } else {
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
-                dest.writeLong(deltaTime);
-            }
-        }
-        if (batteryLevelIntChanged) {
-            dest.writeInt(batteryLevelInt);
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
-                    + Integer.toHexString(batteryLevelInt)
-                    + " batteryLevel=" + cur.batteryLevel
-                    + " batteryTemp=" + cur.batteryTemperature
-                    + " batteryVolt=" + (int)cur.batteryVoltage);
-        }
-        if (stateIntChanged) {
-            dest.writeInt(stateInt);
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
-                    + Integer.toHexString(stateInt)
-                    + " batteryStatus=" + cur.batteryStatus
-                    + " batteryHealth=" + cur.batteryHealth
-                    + " batteryPlugType=" + cur.batteryPlugType
-                    + " states=0x" + Integer.toHexString(cur.states));
-        }
-        if (state2IntChanged) {
-            dest.writeInt(cur.states2);
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x"
-                    + Integer.toHexString(cur.states2));
-        }
-        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
-            int wakeLockIndex;
-            int wakeReasonIndex;
-            if (cur.wakelockTag != null) {
-                wakeLockIndex = writeHistoryTag(cur.wakelockTag);
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
-                    + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
-            } else {
-                wakeLockIndex = 0xffff;
-            }
-            if (cur.wakeReasonTag != null) {
-                wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
-                    + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
-            } else {
-                wakeReasonIndex = 0xffff;
-            }
-            dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
-            if (cur.wakelockTag != null
-                    && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.wakelockTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-            if (cur.wakeReasonTag != null
-                    && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.wakeReasonTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-        }
-        if (cur.eventCode != HistoryItem.EVENT_NONE) {
-            final int index = writeHistoryTag(cur.eventTag);
-            final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
-            dest.writeInt(codeAndIndex);
-            if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.eventTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
-                    + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
-                    + cur.eventTag.string);
-        }
-        if (computeStepDetails) {
             if (mPlatformIdleStateCallback != null) {
-                mCurHistoryStepDetails.statSubsystemPowerState =
+                mDetails.statSubsystemPowerState =
                         mPlatformIdleStateCallback.getSubsystemLowPowerStats();
                 if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
-                        mCurHistoryStepDetails.statSubsystemPowerState);
-
+                        mDetails.statSubsystemPowerState);
             }
-            computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
-            if (includeStepDetails != 0) {
-                mCurHistoryStepDetails.writeToParcel(dest);
-            }
-            cur.stepDetails = mCurHistoryStepDetails;
-            mLastHistoryStepDetails = mCurHistoryStepDetails;
-        } else {
-            cur.stepDetails = null;
-        }
-        if (mLastHistoryStepLevel < cur.batteryLevel) {
-            mLastHistoryStepDetails = null;
-        }
-        mLastHistoryStepLevel = cur.batteryLevel;
 
-        if (batteryChargeChanged) {
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
-            dest.writeInt(cur.batteryChargeUah);
-        }
-        dest.writeDouble(cur.modemRailChargeMah);
-        dest.writeDouble(cur.wifiRailChargeMah);
-    }
-
-    private int buildBatteryLevelInt(HistoryItem h) {
-        return ((((int)h.batteryLevel)<<25)&0xfe000000)
-                | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
-                | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
-    }
-
-    private int buildStateInt(HistoryItem h) {
-        int plugType = 0;
-        if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
-            plugType = 1;
-        } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
-            plugType = 2;
-        } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
-            plugType = 3;
-        }
-        return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
-                | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
-                | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
-                | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
-    }
-
-    private void computeHistoryStepDetails(final HistoryStepDetails out,
-            final HistoryStepDetails last) {
-        final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
-
-        // Perform a CPU update right after we do this collection, so we have started
-        // collecting good data for the next step.
-        requestImmediateCpuUpdate();
-
-        if (last == null) {
-            // We are not generating a delta, so all we need to do is reset the stats
-            // we will later be doing a delta from.
-            final int NU = mUidStats.size();
-            for (int i=0; i<NU; i++) {
-                final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
-                uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
-                uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
-            }
-            mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
-            mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
-            mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
-            mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
-            mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
-            mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
-            mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
-            mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
-            tmp.clear();
-            return;
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
-                    + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
-                    + " irq=" + mLastStepStatIrqTimeMs + " sirq="
-                    + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
-            Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
-                    + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
-                    + " irq=" + mCurStepStatIrqTimeMs + " sirq="
-                    + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
-        }
-        out.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
-        out.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
-        out.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
-        out.statSystemTime = (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
-        out.statIOWaitTime = (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
-        out.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
-        out.statSoftIrqTime = (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
-        out.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
-        out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
-        out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
-        out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
-        final int NU = mUidStats.size();
-        for (int i=0; i<NU; i++) {
-            final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
-            final int totalUTimeMs = (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
-            final int totalSTimeMs = (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
-            final int totalTimeMs = totalUTimeMs + totalSTimeMs;
-            uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
-            uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
-            if (totalTimeMs <= (out.appCpuUTime3 + out.appCpuSTime3)) {
-                continue;
-            }
-            if (totalTimeMs <= (out.appCpuUTime2 + out.appCpuSTime2)) {
-                out.appCpuUid3 = uid.mUid;
-                out.appCpuUTime3 = totalUTimeMs;
-                out.appCpuSTime3 = totalSTimeMs;
-            } else {
-                out.appCpuUid3 = out.appCpuUid2;
-                out.appCpuUTime3 = out.appCpuUTime2;
-                out.appCpuSTime3 = out.appCpuSTime2;
-                if (totalTimeMs <= (out.appCpuUTime1 + out.appCpuSTime1)) {
-                    out.appCpuUid2 = uid.mUid;
-                    out.appCpuUTime2 = totalUTimeMs;
-                    out.appCpuSTime2 = totalSTimeMs;
-                } else {
-                    out.appCpuUid2 = out.appCpuUid1;
-                    out.appCpuUTime2 = out.appCpuUTime1;
-                    out.appCpuSTime2 = out.appCpuSTime1;
-                    out.appCpuUid1 = uid.mUid;
-                    out.appCpuUTime1 = totalUTimeMs;
-                    out.appCpuSTime1 = totalSTimeMs;
+            if (!mHasHistoryStepDetails) {
+                // We are not generating a delta, so all we need to do is reset the stats
+                // we will later be doing a delta from.
+                final int uidCount = mUidStats.size();
+                for (int i = 0; i < uidCount; i++) {
+                    final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                    uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+                    uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
                 }
+                mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+                mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+                mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+                mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+                mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+                mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+                mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+                mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+                mDetails.clear();
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
+                            + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
+                            + " irq=" + mLastStepStatIrqTimeMs + " sirq="
+                            + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
+                    Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
+                            + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
+                            + " irq=" + mCurStepStatIrqTimeMs + " sirq="
+                            + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
+                }
+                mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
+                mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
+                mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
+                mDetails.statSystemTime =
+                        (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
+                mDetails.statIOWaitTime =
+                        (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
+                mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
+                mDetails.statSoftIrqTime =
+                        (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
+                mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
+                mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1;
+                mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0;
+                mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0;
+                final int uidCount = mUidStats.size();
+                for (int i = 0; i < uidCount; i++) {
+                    final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                    final int totalUTimeMs =
+                            (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
+                    final int totalSTimeMs =
+                            (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
+                    final int totalTimeMs = totalUTimeMs + totalSTimeMs;
+                    uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+                    uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+                    if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) {
+                        continue;
+                    }
+                    if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) {
+                        mDetails.appCpuUid3 = uid.mUid;
+                        mDetails.appCpuUTime3 = totalUTimeMs;
+                        mDetails.appCpuSTime3 = totalSTimeMs;
+                    } else {
+                        mDetails.appCpuUid3 = mDetails.appCpuUid2;
+                        mDetails.appCpuUTime3 = mDetails.appCpuUTime2;
+                        mDetails.appCpuSTime3 = mDetails.appCpuSTime2;
+                        if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) {
+                            mDetails.appCpuUid2 = uid.mUid;
+                            mDetails.appCpuUTime2 = totalUTimeMs;
+                            mDetails.appCpuSTime2 = totalSTimeMs;
+                        } else {
+                            mDetails.appCpuUid2 = mDetails.appCpuUid1;
+                            mDetails.appCpuUTime2 = mDetails.appCpuUTime1;
+                            mDetails.appCpuSTime2 = mDetails.appCpuSTime1;
+                            mDetails.appCpuUid1 = uid.mUid;
+                            mDetails.appCpuUTime1 = totalUTimeMs;
+                            mDetails.appCpuSTime1 = totalSTimeMs;
+                        }
+                    }
+                }
+                mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+                mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+                mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+                mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+                mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+                mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+                mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+                mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
             }
+
+            mHasHistoryStepDetails = mBatteryLevel <= mLastHistoryStepLevel;
+            mLastHistoryStepLevel = mBatteryLevel;
+
+            return mDetails;
         }
-        mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
-        mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
-        mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
-        mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
-        mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
-        mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
-        mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
-        mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+
+        public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
+                int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+                int statSoftIrqTimeMs, int statIdleTimeMs) {
+            if (DEBUG) {
+                Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
+                        + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
+                        + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
+                        + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
+            }
+            mCurStepCpuUserTimeMs += totalUTimeMs;
+            mCurStepCpuSystemTimeMs += totalSTimeMs;
+            mCurStepStatUserTimeMs += statUserTimeMs;
+            mCurStepStatSystemTimeMs += statSystemTimeMs;
+            mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
+            mCurStepStatIrqTimeMs += statIrqTimeMs;
+            mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
+            mCurStepStatIdleTimeMs += statIdleTimeMs;
+        }
+
+        @Override
+        public void clear() {
+            mHasHistoryStepDetails = false;
+            mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
+            mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
+            mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
+            mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
+            mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
+            mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
+            mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
+            mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
+        }
     }
 
     @GuardedBy("this")
     @Override
     public void commitCurrentHistoryBatchLocked() {
-        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+        mHistory.commitCurrentHistoryBatchLocked();
     }
 
     @GuardedBy("this")
@@ -4326,191 +4066,9 @@
     }
 
     @GuardedBy("this")
-    void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
-        if (!mHaveBatteryLevel || !mRecordingHistory) {
-            return;
-        }
-
-        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
-        final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates);
-        final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2);
-        final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
-        final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
-        if (DEBUG) {
-            Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
-                    + Integer.toHexString(diffStates) + " lastDiff="
-                    + Integer.toHexString(lastDiffStates) + " diff2="
-                    + Integer.toHexString(diffStates2) + " lastDiff2="
-                    + Integer.toHexString(lastDiffStates2));
-        }
-        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
-                && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
-                && (diffStates2&lastDiffStates2) == 0
-                && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
-                && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
-                && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
-                && mHistoryLastWritten.stepDetails == null
-                && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
-                        || cur.eventCode == HistoryItem.EVENT_NONE)
-                && mHistoryLastWritten.batteryLevel == cur.batteryLevel
-                && mHistoryLastWritten.batteryStatus == cur.batteryStatus
-                && mHistoryLastWritten.batteryHealth == cur.batteryHealth
-                && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
-                && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
-                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
-            // We can merge this new change in with the last one.  Merging is
-            // allowed as long as only the states have changed, and within those states
-            // 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);
-            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
-            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
-            mHistoryBufferLastPos = -1;
-            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
-            // If the last written history had a wakelock tag, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have a wakelock tag.
-            if (mHistoryLastWritten.wakelockTag != null) {
-                cur.wakelockTag = cur.localWakelockTag;
-                cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
-            }
-            // If the last written history had a wake reason tag, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have a wakelock tag.
-            if (mHistoryLastWritten.wakeReasonTag != null) {
-                cur.wakeReasonTag = cur.localWakeReasonTag;
-                cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
-            }
-            // If the last written history had an event, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have an event.
-            if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
-                cur.eventCode = mHistoryLastWritten.eventCode;
-                cur.eventTag = cur.localEventTag;
-                cur.eventTag.setTo(mHistoryLastWritten.eventTag);
-            }
-            mHistoryLastWritten.setTo(mHistoryLastLastWritten);
-        }
-        final int dataSize = mHistoryBuffer.dataSize();
-
-        if (dataSize >= mConstants.MAX_HISTORY_BUFFER) {
-            //open a new history file.
-            final long start = SystemClock.uptimeMillis();
-            writeHistoryLocked();
-            if (DEBUG) {
-                Slog.d(TAG, "addHistoryBufferLocked writeHistoryLocked takes ms:"
-                        + (SystemClock.uptimeMillis() - start));
-            }
-            mBatteryStatsHistory.startNextFile();
-            mHistoryBuffer.setDataSize(0);
-            mHistoryBuffer.setDataPosition(0);
-            mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
-            mHistoryBufferLastPos = -1;
-            mHistoryLastWritten.clear();
-            mHistoryLastLastWritten.clear();
-
-            // Mark every entry in the pool with a flag indicating that the tag
-            // has not yet been encountered while writing the current history buffer.
-            for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
-                entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
-            }
-            // Make a copy of mHistoryCur.
-            HistoryItem copy = new HistoryItem();
-            copy.setTo(cur);
-            // startRecordingHistory will reset mHistoryCur.
-            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
-            // Add the copy into history buffer.
-            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, copy);
-            return;
-        }
-
-        if (dataSize == 0) {
-            // The history is currently empty; we need it to start with a time stamp.
-            cur.currentTime = mClock.currentTimeMillis();
-            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur);
-        }
-        addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
-    }
-
-    @GuardedBy("this")
-    private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
-        if (mBatteryStatsHistoryIterator != null) {
-            throw new IllegalStateException("Can't do this while iterating history!");
-        }
-        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
-        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
-        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
-        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
-        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
-        mHistoryLastWritten.states &= mActiveHistoryStates;
-        mHistoryLastWritten.states2 &= mActiveHistoryStates2;
-        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
-        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-        cur.wakelockTag = null;
-        cur.wakeReasonTag = null;
-        cur.eventCode = HistoryItem.EVENT_NONE;
-        cur.eventTag = null;
-        cur.tagsFirstOccurrence = false;
-        if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
-                + " now " + mHistoryBuffer.dataPosition()
-                + " size is now " + mHistoryBuffer.dataSize());
-    }
-
-    @GuardedBy("this")
-    void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
-        if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
-            final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
-            final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
-            if (diffUptimeMs < (diffElapsedMs - 20)) {
-                final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
-                mHistoryAddTmp.setTo(mHistoryLastWritten);
-                mHistoryAddTmp.wakelockTag = null;
-                mHistoryAddTmp.wakeReasonTag = null;
-                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
-                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
-                addHistoryRecordInnerLocked(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
-            }
-        }
-        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
-        mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-        mTrackRunningHistoryUptimeMs = uptimeMs;
-        addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
-    }
-
-    @GuardedBy("this")
-    void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
-        addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
-    }
-
-    @GuardedBy("this")
-    public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+    public void recordHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
             String name, int uid) {
-        mHistoryCur.eventCode = code;
-        mHistoryCur.eventTag = mHistoryCur.localEventTag;
-        mHistoryCur.eventTag.string = name;
-        mHistoryCur.eventTag.uid = uid;
-        addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
-    }
-
-    @GuardedBy("this")
-    void clearHistoryLocked() {
-        if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
-        mHistoryBaseTimeMs = 0;
-        mLastHistoryElapsedRealtimeMs = 0;
-        mTrackRunningHistoryElapsedRealtimeMs = 0;
-        mTrackRunningHistoryUptimeMs = 0;
-
-        mHistoryBuffer.setDataSize(0);
-        mHistoryBuffer.setDataPosition(0);
-        mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
-        mHistoryLastLastWritten.clear();
-        mHistoryLastWritten.clear();
-        mHistoryTagPool.clear();
-        mNextHistoryTagIdx = 0;
-        mNumHistoryTagChars = 0;
-        mHistoryBufferLastPos = -1;
-        mActiveHistoryStates = 0xffffffff;
-        mActiveHistoryStates2 = 0xffffffff;
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
     }
 
     @GuardedBy("this")
@@ -4663,13 +4221,13 @@
         if (!mActiveEvents.updateState(code, name, uid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
     }
 
     @GuardedBy("this")
     public void noteCurrentTimeChangedLocked(long currentTimeMs,
             long elapsedRealtimeMs, long uptimeMs) {
-        recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs);
+        mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
     }
 
     @GuardedBy("this")
@@ -4686,7 +4244,7 @@
         if (!mRecordAllHistory) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4744,8 +4302,7 @@
         if (!mRecordAllHistory) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH,
-                name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, name, uid);
     }
 
     @GuardedBy("this")
@@ -4761,7 +4318,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4777,8 +4334,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH,
-                name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, name, uid);
     }
 
     @GuardedBy("this")
@@ -4794,7 +4350,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4812,7 +4368,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
     }
 
     @GuardedBy("this")
@@ -4860,7 +4416,7 @@
             for (int i = 0; i < workSource.size(); ++i) {
                 uid = mapUid(workSource.getUid(i));
                 if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
                 }
             }
 
@@ -4869,7 +4425,7 @@
                 for (int i = 0; i < workChains.size(); ++i) {
                     uid = mapUid(workChains.get(i).getAttributionUid());
                     if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
                     }
                 }
             }
@@ -4877,7 +4433,7 @@
             uid = mapUid(uid);
 
             if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
             }
         }
     }
@@ -4952,7 +4508,7 @@
                 for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                     SparseIntArray uids = ent.getValue();
                     for (int j=0; j<uids.size(); j++) {
-                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                        mHistory.recordEvent(mSecRealtime, mSecUptime,
                                 HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
                     }
                 }
@@ -4967,8 +4523,8 @@
                 for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                     SparseIntArray uids = ent.getValue();
                     for (int j=0; j<uids.size(); j++) {
-                        addHistoryEventLocked(mSecRealtime, mSecUptime,
-                                HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+                        mHistory.recordEvent(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_START,
+                                ent.getKey(), uids.keyAt(j));
                     }
                 }
             }
@@ -5011,30 +4567,19 @@
             if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
                         mappedUid, 0)) {
-                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
                             HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
-                mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
-                        + Integer.toHexString(mHistoryCur.states));
-                mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-                mHistoryCur.wakelockTag.string = historyName;
-                mHistoryCur.wakelockTag.uid = mappedUid;
                 mWakeLockImportant = !unimportantForLogging;
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
-            } else if (!mWakeLockImportant && !unimportantForLogging
-                    && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
-                if (mHistoryLastWritten.wakelockTag != null) {
-                    // We'll try to update the last tag.
-                    mHistoryLastWritten.wakelockTag = null;
-                    mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-                    mHistoryCur.wakelockTag.string = historyName;
-                    mHistoryCur.wakelockTag.uid = mappedUid;
-                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.recordWakelockStartEvent(elapsedRealtimeMs, uptimeMs, historyName,
+                        mappedUid);
+            } else if (!mWakeLockImportant && !unimportantForLogging) {
+                if (mHistory.maybeUpdateWakelockTag(elapsedRealtimeMs, uptimeMs, historyName,
+                        mappedUid)) {
+                    mWakeLockImportant = true;
                 }
-                mWakeLockImportant = true;
             }
             mWakeLockNesting++;
         }
@@ -5087,15 +4632,13 @@
                 }
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
                         mappedUid, 0)) {
-                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
                             HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
-                mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
-                        + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE_WAKE_LOCK_FLAG);
             }
         }
         if (mappedUid >= 0) {
@@ -5286,7 +4829,7 @@
                 mappedUid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
                 historyName, mappedUid);
         if (mappedUid != uid) {
             // Prevent the isolated uid mapping from being removed while the wakelock is
@@ -5339,7 +4882,7 @@
                 mappedUid, 0)) {
             return;
         }
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
                 historyName, mappedUid);
         if (mappedUid != uid) {
             // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -5361,15 +4904,10 @@
 
     @GuardedBy("this")
     public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
-        if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
-                + Integer.toHexString(mHistoryCur.states));
         aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
-        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
-        mHistoryCur.wakeReasonTag.string = reason;
-        mHistoryCur.wakeReasonTag.uid = 0;
+        mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
         mLastWakeupReason = reason;
         mLastWakeupUptimeMs = uptimeMs;
-        addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
     }
 
     @GuardedBy("this")
@@ -5380,22 +4918,11 @@
 
     @GuardedBy("this")
     public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
-                                      int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
-                                      int statSoftIrqTimeMs, int statIdleTimeMs) {
-        if (DEBUG) {
-            Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
-                    + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
-                    + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
-                    + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
-        }
-        mCurStepCpuUserTimeMs += totalUTimeMs;
-        mCurStepCpuSystemTimeMs += totalSTimeMs;
-        mCurStepStatUserTimeMs += statUserTimeMs;
-        mCurStepStatSystemTimeMs += statSystemTimeMs;
-        mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
-        mCurStepStatIrqTimeMs += statIrqTimeMs;
-        mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
-        mCurStepStatIdleTimeMs += statIdleTimeMs;
+            int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+            int statSoftIrqTimeMs, int statIdleTimeMs) {
+        mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs,
+                statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs,
+                statSoftIrqTimeMs, statIdleTimeMs);
     }
 
     public void noteProcessDiedLocked(int uid, int pid) {
@@ -5425,10 +4952,8 @@
     public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mSensorNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_SENSOR_ON_FLAG);
         }
         mSensorNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -5445,10 +4970,8 @@
         uid = mapUid(uid);
         mSensorNesting--;
         if (mSensorNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_SENSOR_ON_FLAG);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteStopSensor(sensor, elapsedRealtimeMs);
@@ -5498,10 +5021,8 @@
         }
         final int mappedUid = mapUid(uid);
         if (mGpsNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_GPS_ON_FLAG);
         }
         mGpsNesting++;
 
@@ -5526,10 +5047,8 @@
         final int mappedUid = mapUid(uid);
         mGpsNesting--;
         if (mGpsNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_GPS_ON_FLAG);
             stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
             mGpsSignalQualityBin = -1;
         }
@@ -5562,12 +5081,9 @@
             if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) {
                 mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtimeMs);
             }
-            mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
-                    | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs, signalLevel);
             mGpsSignalQualityBin = signalLevel;
         }
-        return;
     }
 
     @GuardedBy("this")
@@ -5740,41 +5256,33 @@
                 }
             }
 
-            boolean updateHistory = false;
+            int startStates = 0;
+            int stopStates = 0;
             if (Display.isDozeState(state) && !Display.isDozeState(oldState)) {
-                mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                startStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.startRunningLocked(elapsedRealtimeMs);
-                updateHistory = true;
             } else if (Display.isDozeState(oldState) && !Display.isDozeState(state)) {
-                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                stopStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
-                updateHistory = true;
             }
             if (Display.isOnState(state)) {
-                mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
-                        + Integer.toHexString(mHistoryCur.states));
+                startStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
                 mScreenOnTimer.startRunningLocked(elapsedRealtimeMs);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin]
                             .startRunningLocked(elapsedRealtimeMs);
                 }
-                updateHistory = true;
             } else if (Display.isOnState(oldState)) {
-                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
-                        + Integer.toHexString(mHistoryCur.states));
+                stopStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
                 mScreenOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin]
                             .stopRunningLocked(elapsedRealtimeMs);
                 }
-                updateHistory = true;
             }
-            if (updateHistory) {
-                if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
-                        + Display.stateToString(state));
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            if (startStates != 0 || stopStates != 0) {
+                mHistory.recordStateChangeEvent(elapsedRealtimeMs, uptimeMs, startStates,
+                        stopStates);
             }
 
             // Per screen state Cpu stats needed. Prepare to schedule an external sync.
@@ -5888,13 +5396,7 @@
             long uptimeMs) {
         if (mScreenBrightnessBin != overallBin) {
             if (overallBin >= 0) {
-                mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
-                        | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
-                if (DEBUG_HISTORY) {
-                    Slog.v(TAG, "Screen brightness " + overallBin + " to: "
-                            + Integer.toHexString(mHistoryCur.states));
-                }
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.recordScreenBrightnessEvent(elapsedRealtimeMs, uptimeMs, overallBin);
             }
             if (mScreenState == Display.STATE_ON) {
                 if (mScreenBrightnessBin >= 0) {
@@ -5921,8 +5423,8 @@
     @GuardedBy("this")
     public void noteWakeUpLocked(String reason, int reasonUid,
             long elapsedRealtimeMs, long uptimeMs) {
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP,
-                reason, reasonUid);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, reason,
+                reasonUid);
     }
 
     @GuardedBy("this")
@@ -5941,7 +5443,7 @@
     @GuardedBy("this")
     public void noteConnectivityChangedLocked(int type, String extra,
             long elapsedRealtimeMs, long uptimeMs) {
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
                 extra, type);
         mNumConnectivityChange++;
     }
@@ -5950,7 +5452,7 @@
     private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
-        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+        mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
                 uid);
         getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteMobileRadioApWakeupLocked();
     }
@@ -5976,7 +5478,8 @@
                 }
 
                 mMobileRadioActiveStartTimeMs = realElapsedRealtimeMs = timestampNs / (1000 * 1000);
-                mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+                mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
             } else {
                 realElapsedRealtimeMs = timestampNs / (1000*1000);
                 long lastUpdateTimeMs = mMobileRadioActiveStartTimeMs;
@@ -5988,11 +5491,9 @@
                     mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtimeMs
                             - realElapsedRealtimeMs);
                 }
-                mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
             }
-            if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mMobileRadioPowerState = powerState;
 
             // Inform current RatBatteryStats that the modem active state might have changed.
@@ -6042,17 +5543,14 @@
             mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             mPowerSaveModeEnabled = enabled;
             if (enabled) {
-                mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
-                        + Integer.toHexString(mHistoryCur.states2));
+                mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE2_POWER_SAVE_FLAG);
                 mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtimeMs);
             } else {
-                mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
-                        + Integer.toHexString(mHistoryCur.states2));
+                mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE2_POWER_SAVE_FLAG);
                 mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs);
             }
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
                     enabled
                         ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
@@ -6076,7 +5574,7 @@
             nowLightIdling = true;
         }
         if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
-            addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
+            mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
                     activeReason, activeUid);
         }
         if (mDeviceIdling != nowIdling || mDeviceLightIdling != nowLightIdling) {
@@ -6106,11 +5604,7 @@
             }
         }
         if (mDeviceIdleMode != mode) {
-            mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
-                    | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
-            if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordDeviceIdleEvent(elapsedRealtimeMs, uptimeMs, mode);
             long lastDuration = elapsedRealtimeMs - mLastIdleTimeStartMs;
             mLastIdleTimeStartMs = elapsedRealtimeMs;
             if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
@@ -6138,7 +5632,7 @@
     public void notePackageInstalledLocked(String pkgName, long versionCode,
             long elapsedRealtimeMs, long uptimeMs) {
         // XXX need to figure out what to do with long version codes.
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
                 pkgName, (int)versionCode);
         PackageChange pc = new PackageChange();
         pc.mPackageName = pkgName;
@@ -6150,8 +5644,8 @@
     @GuardedBy("this")
     public void notePackageUninstalledLocked(String pkgName,
             long elapsedRealtimeMs, long uptimeMs) {
-        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
-                HistoryItem.EVENT_PACKAGE_UNINSTALLED, pkgName, 0);
+        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
+                pkgName, 0);
         PackageChange pc = new PackageChange();
         pc.mPackageName = pkgName;
         pc.mUpdate = true;
@@ -6180,10 +5674,8 @@
     @GuardedBy("this")
     public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mPhoneOn) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
             mPhoneOn = true;
             mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
@@ -6192,10 +5684,8 @@
     @GuardedBy("this")
     public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mPhoneOn) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
             mPhoneOn = false;
             mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
@@ -6233,11 +5723,12 @@
         if (mUsbDataState != newState) {
             mUsbDataState = newState;
             if (connected) {
-                mHistoryCur.states2 |= HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+                mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE2_USB_DATA_LINK_FLAG);
             } else {
-                mHistoryCur.states2 &= ~HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+                mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE2_USB_DATA_LINK_FLAG);
             }
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -6258,6 +5749,10 @@
             long elapsedRealtimeMs, long uptimeMs) {
         boolean scanning = false;
         boolean newHistory = false;
+        int addStateFlag = 0;
+        int removeStateFlag = 0;
+        int newState = -1;
+        int newSignalStrength = -1;
 
         mPhoneServiceStateRaw = state;
         mPhoneSimStateRaw = simState;
@@ -6286,10 +5781,8 @@
             scanning = true;
             strengthBin = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
             if (!mPhoneSignalScanningTimer.isRunningLocked()) {
-                mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
                 newHistory = true;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
-                        + Integer.toHexString(mHistoryCur.states));
                 mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs);
                 FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
                         simState, strengthBin);
@@ -6299,9 +5792,7 @@
         if (!scanning) {
             // If we are no longer scanning, then stop the scanning timer.
             if (mPhoneSignalScanningTimer.isRunningLocked()) {
-                mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
-                        + Integer.toHexString(mHistoryCur.states));
+                removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
                 newHistory = true;
                 mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs);
                 FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
@@ -6310,10 +5801,7 @@
         }
 
         if (mPhoneServiceState != state) {
-            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
-                    | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
-            if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: "
-                    + Integer.toHexString(mHistoryCur.states));
+            newState = state;
             newHistory = true;
             mPhoneServiceState = state;
         }
@@ -6327,11 +5815,7 @@
                 if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
                 }
-                mHistoryCur.states =
-                        (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
-                        | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
-                if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
-                        + Integer.toHexString(mHistoryCur.states));
+                newSignalStrength = strengthBin;
                 newHistory = true;
                 FrameworkStatsLog.write(
                         FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
@@ -6342,7 +5826,8 @@
         }
 
         if (newHistory) {
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordPhoneStateChangeEvent(elapsedRealtimeMs, uptimeMs,
+                    addStateFlag, removeStateFlag, newState, newSignalStrength);
         }
     }
 
@@ -6466,11 +5951,7 @@
 
         if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
         if (mPhoneDataConnectionType != bin) {
-            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
-                    | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
-            if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordDataConnectionTypeChangeEvent(elapsedRealtimeMs, uptimeMs, bin);
             if (mPhoneDataConnectionType >= 0) {
                 mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
                         elapsedRealtimeMs);
@@ -6543,10 +6024,8 @@
     @GuardedBy("this")
     public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mWifiOn) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_WIFI_ON_FLAG);
             mWifiOn = true;
             mWifiOnTimer.startRunningLocked(elapsedRealtimeMs);
             scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
@@ -6556,10 +6035,8 @@
     @GuardedBy("this")
     public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiOn) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_WIFI_ON_FLAG);
             mWifiOn = false;
             mWifiOnTimer.stopRunningLocked(elapsedRealtimeMs);
             scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
@@ -6570,10 +6047,8 @@
     public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mAudioOnNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_AUDIO_ON_FLAG);
             mAudioOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mAudioOnNesting++;
@@ -6588,10 +6063,8 @@
         }
         uid = mapUid(uid);
         if (--mAudioOnNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_AUDIO_ON_FLAG);
             mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6602,10 +6075,8 @@
     public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mVideoOnNesting == 0) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_VIDEO_ON_FLAG);
             mVideoOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mVideoOnNesting++;
@@ -6620,10 +6091,8 @@
         }
         uid = mapUid(uid);
         if (--mVideoOnNesting == 0) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_VIDEO_ON_FLAG);
             mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6634,10 +6103,8 @@
     public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mAudioOnNesting > 0) {
             mAudioOnNesting = 0;
-            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_AUDIO_ON_FLAG);
             mAudioOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6650,10 +6117,8 @@
     public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mVideoOnNesting > 0) {
             mVideoOnNesting = 0;
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_VIDEO_ON_FLAG);
             mVideoOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6705,10 +6170,8 @@
     public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mFlashlightOnNesting++ == 0) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
             mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6722,10 +6185,8 @@
         }
         uid = mapUid(uid);
         if (--mFlashlightOnNesting == 0) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
             mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6736,10 +6197,8 @@
     public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mCameraOnNesting++ == 0) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_CAMERA_FLAG);
             mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6753,10 +6212,8 @@
         }
         uid = mapUid(uid);
         if (--mCameraOnNesting == 0) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_CAMERA_FLAG);
             mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6767,10 +6224,8 @@
     public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mCameraOnNesting > 0) {
             mCameraOnNesting = 0;
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_CAMERA_FLAG);
             mCameraOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6783,10 +6238,8 @@
     public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mFlashlightOnNesting > 0) {
             mFlashlightOnNesting = 0;
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
             mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6803,10 +6256,8 @@
         }
         uid = mapUid(uid);
         if (mBluetoothScanNesting == 0) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
             mBluetoothScanTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mBluetoothScanNesting++;
@@ -6847,10 +6298,8 @@
         uid = mapUid(uid);
         mBluetoothScanNesting--;
         if (mBluetoothScanNesting == 0) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
             mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6885,10 +6334,8 @@
     public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mBluetoothScanNesting > 0) {
             mBluetoothScanNesting = 0;
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
             mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6928,7 +6375,7 @@
     private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
-        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+        mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
                 uid);
         getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked();
     }
@@ -6944,15 +6391,14 @@
                 if (uid > 0) {
                     noteWifiRadioApWakeupLocked(elapsedRealtimeMs, uptimeMs, uid);
                 }
-                mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+                mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
                 mWifiActiveTimer.startRunningLocked(elapsedRealtimeMs);
             } else {
-                mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                        HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
                 mWifiActiveTimer.stopRunningLocked(timestampNs / (1000 * 1000));
             }
-            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mWifiRadioPowerState = powerState;
         }
     }
@@ -6960,10 +6406,8 @@
     @GuardedBy("this")
     public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (!mGlobalWifiRunning) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_WIFI_RUNNING_FLAG);
             mGlobalWifiRunning = true;
             mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
             int N = ws.size();
@@ -7031,10 +6475,8 @@
     @GuardedBy("this")
     public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (mGlobalWifiRunning) {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_WIFI_RUNNING_FLAG);
             mGlobalWifiRunning = false;
             mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
             int N = ws.size();
@@ -7082,12 +6524,7 @@
             }
             mWifiSupplState = supplState;
             mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtimeMs);
-            mHistoryCur.states2 =
-                    (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
-                    | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
-            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
-                    + Integer.toHexString(mHistoryCur.states2));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordWifiSupplicantStateChangeEvent(elapsedRealtimeMs, uptimeMs, supplState);
         }
     }
 
@@ -7116,12 +6553,8 @@
                 if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
                 }
-                mHistoryCur.states2 =
-                        (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
-                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
-                if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
-                        + Integer.toHexString(mHistoryCur.states2));
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.recordWifiSignalStrengthChangeEvent(elapsedRealtimeMs, uptimeMs,
+                        strengthBin);
             } else {
                 stopAllWifiSignalStrengthTimersLocked(-1, elapsedRealtimeMs);
             }
@@ -7134,10 +6567,8 @@
     @GuardedBy("this")
     public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiFullLockNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
         }
         mWifiFullLockNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7148,10 +6579,8 @@
     public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiFullLockNesting--;
         if (mWifiFullLockNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteFullWifiLockReleasedLocked(elapsedRealtimeMs);
@@ -7167,10 +6596,8 @@
     @GuardedBy("this")
     public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiScanNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_SCAN_FLAG);
         }
         mWifiScanNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7186,10 +6613,8 @@
     public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiScanNesting--;
         if (mWifiScanNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_SCAN_FLAG);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteWifiScanStoppedLocked(elapsedRealtimeMs);
@@ -7214,14 +6639,10 @@
     public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mWifiMulticastNesting == 0) {
-            mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
-
+            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
             // Start Wifi Multicast overall timer
             if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
-                if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
                 mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtimeMs);
             }
         }
@@ -7235,14 +6656,12 @@
         uid = mapUid(uid);
         mWifiMulticastNesting--;
         if (mWifiMulticastNesting == 0) {
-            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
 
             // Stop Wifi Multicast overall timer
             if (mWifiMulticastWakelockTimer.isRunningLocked()) {
-                if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+                if (DEBUG) Slog.v(TAG, "Multicast Overall Timer Stopped");
                 mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
             }
         }
@@ -7994,8 +7413,9 @@
             // If the start clock time has changed by more than a year, then presumably
             // the previous time was completely bogus.  So we are going to figure out a
             // new time based on how much time has elapsed since we started counting.
-            recordCurrentTimeChangeLocked(currentTimeMs, mClock.elapsedRealtime(),
-                    mClock.uptimeMillis());
+            mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+                    currentTimeMs
+            );
             return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
         }
         return mStartClockTimeMs;
@@ -11227,18 +10647,19 @@
             UserInfoProvider userInfoProvider) {
         init(clock);
 
+        mHandler = new MyHandler(handler.getLooper());
+        mConstants = new Constants(mHandler);
+
         if (systemDir == null) {
             mStatsFile = null;
-            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
-            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, systemDir,
-                    this::getMaxHistoryFiles);
+            mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         }
         mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
-        mHandler = new MyHandler(handler.getLooper());
-        mConstants = new Constants(mHandler);
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
@@ -11247,7 +10668,6 @@
         initTimes(uptimeUs, realtimeUs);
         mStartPlatformVersion = mEndPlatformVersion = Build.ID;
         initDischarge(realtimeUs);
-        clearHistoryLocked();
         updateDailyDeadlineLocked();
         mPlatformIdleStateCallback = cb;
         mMeasuredEnergyRetriever = energyStatsCb;
@@ -11258,12 +10678,6 @@
         FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
     }
 
-    private int getMaxHistoryFiles() {
-        synchronized (this) {
-            return mConstants.MAX_HISTORY_FILES;
-        }
-    }
-
     @VisibleForTesting
     protected void initTimersAndCounters() {
         mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -11345,7 +10759,7 @@
         mDischargeUnplugLevel = 0;
         mDischargePlugLevel = -1;
         mDischargeCurrentLevel = 0;
-        mCurrentBatteryLevel = 0;
+        mBatteryLevel = 0;
     }
 
     public void setPowerProfileLocked(PowerProfile profile) {
@@ -11732,7 +11146,7 @@
     }
 
     public int getHistoryUsedSize() {
-        return mBatteryStatsHistory.getHistoryUsedSize();
+        return mHistory.getHistoryUsedSize();
     }
 
     @Override
@@ -11746,43 +11160,27 @@
      */
     @VisibleForTesting
     public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
-        return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+        return mHistory.iterate();
     }
 
     @Override
     public int getHistoryStringPoolSize() {
-        return mHistoryTagPool.size();
+        return mHistory.getHistoryStringPoolSize();
     }
 
     @Override
     public int getHistoryStringPoolBytes() {
-        return mNumHistoryTagChars;
+        return mHistory.getHistoryStringPoolBytes();
     }
 
     @Override
     public String getHistoryTagPoolString(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.string : null;
+        return mHistory.getHistoryTagPoolString(index);
     }
 
     @Override
     public int getHistoryTagPoolUid(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
-    }
-
-    private void ensureHistoryTagArray() {
-        if (mHistoryTags != null) {
-            return;
-        }
-
-        mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
-        for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
-            mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
-                    entry.getKey());
-        }
+        return mHistory.getHistoryTagPoolUid(index);
     }
 
     @Override
@@ -11792,15 +11190,11 @@
 
     @Override
     public void finishIteratingHistoryLocked() {
+        mBatteryStatsHistoryIterator.close();
         mBatteryStatsHistoryIterator = null;
     }
 
     @Override
-    public long getHistoryBaseTime() {
-        return mHistoryBaseTimeMs;
-    }
-
-    @Override
     public int getStartCount() {
         return mStartCount;
     }
@@ -11853,24 +11247,23 @@
         long realtimeUs = mSecRealtime * 1000;
         resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND);
         pullPendingStateUpdatesLocked();
-        addHistoryRecordLocked(mSecRealtime, mSecUptime);
-        mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
-                = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
+        mHistory.writeHistoryItem(mSecRealtime, mSecUptime);
+        mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel = mBatteryLevel;
         mOnBatteryTimeBase.reset(uptimeUs, realtimeUs);
         mOnBatteryScreenOffTimeBase.reset(uptimeUs, realtimeUs);
-        if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+        if (!mBatteryPluggedIn) {
             if (Display.isOnState(mScreenState)) {
-                mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenOnUnplugLevel = mBatteryLevel;
                 mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = 0;
             } else if (Display.isDozeState(mScreenState)) {
                 mDischargeScreenOnUnplugLevel = 0;
-                mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenDozeUnplugLevel = mBatteryLevel;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
                 mDischargeScreenDozeUnplugLevel = 0;
-                mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenOffUnplugLevel = mBatteryLevel;
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
@@ -12014,27 +11407,12 @@
 
         resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
 
-        mLastHistoryStepDetails = null;
-        mLastStepCpuUserTimeMs = mLastStepCpuSystemTimeMs = 0;
-        mCurStepCpuUserTimeMs = mCurStepCpuSystemTimeMs = 0;
-        mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
-        mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
-        mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
-        mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
-        mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
-        mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
-        mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
-        mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
-
         mNumAllUidCpuTimeReads = 0;
         mNumUidsRemoved = 0;
 
         initDischarge(elapsedRealtimeUs);
 
-        clearHistoryLocked();
-        if (mBatteryStatsHistory != null) {
-            mBatteryStatsHistory.resetAllFiles();
-        }
+        mHistory.reset();
 
         // Flush external data, gathering snapshots, but don't process it since it is pre-reset data
         mIgnoreNextExternalStats = true;
@@ -12057,7 +11435,7 @@
             for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                 SparseIntArray uids = ent.getValue();
                 for (int j=0; j<uids.size(); j++) {
-                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
                             uids.keyAt(j));
                 }
             }
@@ -12482,9 +11860,8 @@
                         (long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt);
                 mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
                         monitoredRailChargeConsumedMaMs);
-                mHistoryCur.wifiRailChargeMah +=
-                        (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+                        (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
                 mTmpRailStats.resetWifiTotalEnergyUsed();
 
                 if (uidEstimatedConsumptionMah != null) {
@@ -12597,9 +11974,8 @@
                             (long) (mTmpRailStats.getCellularTotalEnergyUseduWs() / opVolt);
                     mModemActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
                             monitoredRailChargeConsumedMaMs);
-                    mHistoryCur.modemRailChargeMah +=
-                            (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
-                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                    mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+                            (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
                     mTmpRailStats.resetCellularTotalEnergyUsed();
                 }
 
@@ -12867,8 +12243,8 @@
             }
         }
         if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
-            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+                    HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG);
         }
     }
 
@@ -14301,11 +13677,7 @@
         mHandler.removeCallbacks(mDeferSetCharging);
         if (mCharging != charging) {
             mCharging = charging;
-            if (charging) {
-                mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
-            } else {
-                mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
-            }
+            mHistory.setChargingState(charging);
             mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
             return true;
         }
@@ -14319,6 +13691,15 @@
         mSystemReady = true;
     }
 
+    /**
+     * Force recording of all history events regardless of the "charging" state.
+     */
+    @VisibleForTesting
+    public void forceRecordAllHistory() {
+        mHistory.forceRecordAllHistory();
+        mRecordAllHistory = true;
+    }
+
     @GuardedBy("this")
     protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
             final boolean onBattery, final int oldStatus, final int level, final int chargeUah) {
@@ -14402,15 +13783,12 @@
             mInitStepMode = mCurStepMode;
             mModStepMode = 0;
             pullPendingStateUpdatesLocked();
-            mHistoryCur.batteryLevel = (byte)level;
-            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
-                    + Integer.toHexString(mHistoryCur.states));
             if (reset) {
-                mRecordingHistory = true;
-                startRecordingHistory(mSecRealtime, mSecUptime, reset);
+                mHistory.startRecordingHistory(mSecRealtime, mSecUptime, reset);
+                initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
             }
-            addHistoryRecordLocked(mSecRealtime, mSecUptime);
+            mBatteryPluggedIn = false;
+            mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
             if (Display.isOnState(screenState)) {
                 mDischargeScreenOnUnplugLevel = level;
@@ -14432,11 +13810,8 @@
         } else {
             mOnBattery = mOnBatteryInternal = false;
             pullPendingStateUpdatesLocked();
-            mHistoryCur.batteryLevel = (byte)level;
-            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-            if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
-                    + Integer.toHexString(mHistoryCur.states));
-            addHistoryRecordLocked(mSecRealtime, mSecUptime);
+            mBatteryPluggedIn = true;
+            mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
             mDischargeCurrentLevel = mDischargePlugLevel = level;
             if (level < mDischargeUnplugLevel) {
                 mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
@@ -14451,45 +13826,12 @@
             mModStepMode = 0;
         }
         if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) {
-            if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) {
+            if (mStatsFile != null && !mHistory.isReadOnly()) {
                 writeAsyncLocked();
             }
         }
     }
 
-    @GuardedBy("this")
-    private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
-            boolean reset) {
-        mRecordingHistory = true;
-        mHistoryCur.currentTime = mClock.currentTimeMillis();
-        addHistoryBufferLocked(elapsedRealtimeMs,
-                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
-                mHistoryCur);
-        mHistoryCur.currentTime = 0;
-        if (reset) {
-            initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
-        }
-    }
-
-    @GuardedBy("this")
-    private void recordCurrentTimeChangeLocked(final long currentTimeMs,
-            final long elapsedRealtimeMs, final long uptimeMs) {
-        if (mRecordingHistory) {
-            mHistoryCur.currentTime = currentTimeMs;
-            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur);
-            mHistoryCur.currentTime = 0;
-        }
-    }
-
-    @GuardedBy("this")
-    private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) {
-        if (mRecordingHistory) {
-            mHistoryCur.currentTime = currentTimeMs;
-            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur);
-            mHistoryCur.currentTime = 0;
-        }
-    }
-
     private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) {
         if (mExternalSync != null) {
             mExternalSync.scheduleSync(reason, updateFlags);
@@ -14507,8 +13849,7 @@
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
-        reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
-                status, plugType, level);
+        reportChangesToStatsLog(status, plugType, level);
 
         final boolean onBattery = isOnBattery(plugType, status);
         if (!mHaveBatteryLevel) {
@@ -14518,52 +13859,47 @@
             // plugged in, then twiddle our state to correctly reflect that
             // since we won't be going through the full setOnBattery().
             if (onBattery == mOnBattery) {
-                if (onBattery) {
-                    mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-                } else {
-                    mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-                }
+                mHistory.setPluggedInState(!onBattery);
             }
+            mBatteryStatus = status;
+            mBatteryLevel = level;
+            mBatteryChargeUah = chargeUah;
+
             // Always start out assuming charging, that will be updated later.
-            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
-            mHistoryCur.batteryStatus = (byte)status;
-            mHistoryCur.batteryLevel = (byte)level;
-            mHistoryCur.batteryChargeUah = chargeUah;
+            mHistory.setBatteryState(true /* charging */, status, level, chargeUah);
+
             mMaxChargeStepLevel = mMinDischargeStepLevel =
                     mLastChargeStepLevel = mLastDischargeStepLevel = level;
-        } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+        } else if (mBatteryLevel != level || mOnBattery != onBattery) {
             recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs);
         }
-        int oldStatus = mHistoryCur.batteryStatus;
+        int oldStatus = mBatteryStatus;
         if (onBattery) {
             mDischargeCurrentLevel = level;
-            if (!mRecordingHistory) {
-                mRecordingHistory = true;
-                startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+            if (!mHistory.isRecordingHistory()) {
+                mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
             }
         } else if (level < 96 &&
                 status != BatteryManager.BATTERY_STATUS_UNKNOWN) {
-            if (!mRecordingHistory) {
-                mRecordingHistory = true;
-                startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+            if (!mHistory.isRecordingHistory()) {
+                mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
             }
         }
-        mBatteryVoltageMv = voltageMv;
-        mCurrentBatteryLevel = level;
         if (mDischargePlugLevel < 0) {
             mDischargePlugLevel = level;
         }
 
         if (onBattery != mOnBattery) {
-            mHistoryCur.batteryLevel = (byte)level;
-            mHistoryCur.batteryStatus = (byte)status;
-            mHistoryCur.batteryHealth = (byte)health;
-            mHistoryCur.batteryPlugType = (byte)plugType;
-            mHistoryCur.batteryTemperature = (short)temp;
-            mHistoryCur.batteryVoltage = (char) voltageMv;
-            if (chargeUah < mHistoryCur.batteryChargeUah) {
+            mBatteryLevel = level;
+            mBatteryStatus = status;
+            mBatteryHealth = health;
+            mBatteryPlugType = plugType;
+            mBatteryTemperature = temp;
+            mBatteryVoltageMv = voltageMv;
+            mHistory.setBatteryState(status, level, health, plugType, temp, voltageMv, chargeUah);
+            if (chargeUah < mBatteryChargeUah) {
                 // Only record discharges
-                final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+                final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
                 mDischargeCounter.addCountLocked(chargeDiff);
                 mDischargeScreenOffCounter.addCountLocked(chargeDiff);
                 if (Display.isDozeState(mScreenState)) {
@@ -14575,12 +13911,12 @@
                     mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
                 }
             }
-            mHistoryCur.batteryChargeUah = chargeUah;
+            mBatteryChargeUah = chargeUah;
             setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah);
         } else {
             boolean changed = false;
-            if (mHistoryCur.batteryLevel != level) {
-                mHistoryCur.batteryLevel = (byte)level;
+            if (mBatteryLevel != level) {
+                mBatteryLevel = level;
                 changed = true;
 
                 // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
@@ -14588,33 +13924,33 @@
                 mExternalSync.scheduleSyncDueToBatteryLevelChange(
                         mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS);
             }
-            if (mHistoryCur.batteryStatus != status) {
-                mHistoryCur.batteryStatus = (byte)status;
+            if (mBatteryStatus != status) {
+                mBatteryStatus = status;
                 changed = true;
             }
-            if (mHistoryCur.batteryHealth != health) {
-                mHistoryCur.batteryHealth = (byte)health;
+            if (mBatteryHealth != health) {
+                mBatteryHealth = health;
                 changed = true;
             }
-            if (mHistoryCur.batteryPlugType != plugType) {
-                mHistoryCur.batteryPlugType = (byte)plugType;
+            if (mBatteryPlugType != plugType) {
+                mBatteryPlugType = plugType;
                 changed = true;
             }
-            if (temp >= (mHistoryCur.batteryTemperature+10)
-                    || temp <= (mHistoryCur.batteryTemperature-10)) {
-                mHistoryCur.batteryTemperature = (short)temp;
+            if (temp >= (mBatteryTemperature + 10)
+                    || temp <= (mBatteryTemperature - 10)) {
+                mBatteryTemperature = temp;
                 changed = true;
             }
-            if (voltageMv > (mHistoryCur.batteryVoltage + 20)
-                    || voltageMv < (mHistoryCur.batteryVoltage - 20)) {
-                mHistoryCur.batteryVoltage = (char) voltageMv;
+            if (voltageMv > (mBatteryVoltageMv + 20)
+                    || voltageMv < (mBatteryVoltageMv - 20)) {
+                mBatteryVoltageMv = voltageMv;
                 changed = true;
             }
-            if (chargeUah >= (mHistoryCur.batteryChargeUah + 10)
-                    || chargeUah <= (mHistoryCur.batteryChargeUah - 10)) {
-                if (chargeUah < mHistoryCur.batteryChargeUah) {
+            if (chargeUah >= (mBatteryChargeUah + 10)
+                    || chargeUah <= (mBatteryChargeUah - 10)) {
+                if (chargeUah < mBatteryChargeUah) {
                     // Only record discharges
-                    final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+                    final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
                     mDischargeCounter.addCountLocked(chargeDiff);
                     mDischargeScreenOffCounter.addCountLocked(chargeDiff);
                     if (Display.isDozeState(mScreenState)) {
@@ -14626,9 +13962,10 @@
                         mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
                     }
                 }
-                mHistoryCur.batteryChargeUah = chargeUah;
+                mBatteryChargeUah = chargeUah;
                 changed = true;
             }
+
             long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
                     | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
                     | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
@@ -14686,7 +14023,10 @@
                 mLastChargeStepLevel = level;
             }
             if (changed) {
-                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+                mHistory.setBatteryState(mBatteryStatus, mBatteryLevel, mBatteryHealth,
+                        mBatteryPlugType, mBatteryTemperature, mBatteryVoltageMv,
+                        mBatteryChargeUah);
+                mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
             }
         }
         if (!onBattery &&
@@ -14695,7 +14035,7 @@
             // We don't record history while we are plugged in and fully charged
             // (or when battery is not present).  The next time we are
             // unplugged, history will be cleared.
-            mRecordingHistory = DEBUG;
+            mHistory.setHistoryRecordingEnabled(DEBUG);
         }
 
         mLastLearnedBatteryCapacityUah = chargeFullUah;
@@ -14714,17 +14054,18 @@
     }
 
     // Inform StatsLog of setBatteryState changes.
-    // If this is the first reporting, pass in recentPast == null.
-    private void reportChangesToStatsLog(HistoryItem recentPast,
-            final int status, final int plugType, final int level) {
+    private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
+        if (!mHaveBatteryLevel) {
+            return;
+        }
 
-        if (recentPast == null || recentPast.batteryStatus != status) {
+        if (mBatteryStatus != status) {
             FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
         }
-        if (recentPast == null || recentPast.batteryPlugType != plugType) {
+        if (mBatteryPlugType != plugType) {
             FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
         }
-        if (recentPast == null || recentPast.batteryLevel != level) {
+        if (mBatteryLevel != level) {
             FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
         }
     }
@@ -14794,7 +14135,7 @@
         if (msPerLevel <= 0) {
             return -1;
         }
-        return (msPerLevel * mCurrentBatteryLevel) * 1000;
+        return (msPerLevel * mBatteryLevel) * 1000;
     }
 
     @Override
@@ -14824,7 +14165,7 @@
         if (msPerLevel <= 0) {
             return -1;
         }
-        return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000;
+        return (msPerLevel * (100 - mBatteryLevel)) * 1000;
     }
 
     /*@hide */
@@ -15255,7 +14596,8 @@
 
     @GuardedBy("this")
     public void shutdownLocked() {
-        recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime());
+        mHistory.recordShutdownEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+                mClock.currentTimeMillis());
         writeSyncLocked();
         mShuttingDown = true;
     }
@@ -15463,7 +14805,6 @@
                 PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
                         KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
                         DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
-
                 MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
                         ActivityManager.isLowRamDeviceStatic() ?
                                 DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
@@ -15474,9 +14815,20 @@
                                 : DEFAULT_MAX_HISTORY_BUFFER_KB)
                         * 1024;
                 updateBatteryChargedDelayMsLocked();
+
+                onChange();
             }
         }
 
+        /**
+         * Propagates changes in constant values.
+         */
+        @VisibleForTesting
+        public void onChange() {
+            mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES);
+            mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER);
+        }
+
         private void updateBatteryChargedDelayMsLocked() {
             // a negative value indicates that we should ignore this override
             final int delay = Settings.Global.getInt(mResolver,
@@ -15697,27 +15049,11 @@
     }
 
     private void writeHistoryLocked() {
-        if (mBatteryStatsHistory.getActiveFile() == null) {
-            Slog.w(TAG, "writeHistoryLocked: no history file associated with this instance");
-            return;
-        }
-
         if (mShuttingDown) {
             return;
         }
 
-        Parcel p = Parcel.obtain();
-        try {
-            final long start = SystemClock.uptimeMillis();
-            writeHistoryBuffer(p, true);
-            if (DEBUG) {
-                Slog.d(TAG, "writeHistoryBuffer duration ms:"
-                        + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
-            }
-            writeParcelToFileLocked(p, mBatteryStatsHistory.getActiveFile());
-        } finally {
-            p.recycle();
-        }
+        mHistory.writeHistory();
     }
 
     private final ReentrantLock mWriteLock = new ReentrantLock();
@@ -15756,13 +15092,6 @@
             return;
         }
 
-        final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
-        if (activeHistoryFile == null) {
-            Slog.w(TAG,
-                    "readLocked: no history file associated with this instance");
-            return;
-        }
-
         mUidStats.clear();
 
         Parcel stats = Parcel.obtain();
@@ -15775,7 +15104,7 @@
                 readSummaryFromParcel(stats);
                 if (DEBUG) {
                     Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
-                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
                             - start));
                 }
             }
@@ -15787,126 +15116,19 @@
             stats.recycle();
         }
 
-        Parcel history = Parcel.obtain();
-        try {
-            final long start = SystemClock.uptimeMillis();
-            if (activeHistoryFile.exists()) {
-                byte[] raw = activeHistoryFile.readFully();
-                if (raw.length > 0) {
-                    history.unmarshall(raw, 0, raw.length);
-                    history.setDataPosition(0);
-                    readHistoryBuffer(history);
-                }
-                if (DEBUG) {
-                    Slog.d(TAG, "readLocked history file::"
-                            + activeHistoryFile.getBaseFile().getPath()
-                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
-                            - start));
-                }
-            }
-        } catch (Exception e) {
-            Slog.e(TAG, "Error reading battery history", e);
-            clearHistoryLocked();
-            mBatteryStatsHistory.resetAllFiles();
-        } finally {
-            history.recycle();
+        if (!mHistory.readSummary()) {
+            resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+                    RESET_REASON_CORRUPT_FILE);
         }
 
         mEndPlatformVersion = Build.ID;
 
-        if (mHistoryBuffer.dataPosition() > 0
-                || mBatteryStatsHistory.getFilesNumbers().size() > 1) {
-            mRecordingHistory = true;
-            final long elapsedRealtimeMs = mClock.elapsedRealtime();
-            final long uptimeMs = mClock.uptimeMillis();
-            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_START, mHistoryCur);
-            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
-        }
+        mHistory.continueRecordingHistory();
 
         recordDailyStatsIfNeededLocked(false, mClock.currentTimeMillis());
     }
 
     @GuardedBy("this")
-    void  readHistoryBuffer(Parcel in) throws ParcelFormatException {
-        final int version = in.readInt();
-        if (version != BatteryStatsHistory.VERSION) {
-            Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
-                    + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
-            return;
-        }
-
-        final long historyBaseTime = in.readLong();
-
-        mHistoryBuffer.setDataSize(0);
-        mHistoryBuffer.setDataPosition(0);
-
-        int bufSize = in.readInt();
-        int curPos = in.dataPosition();
-        if (bufSize >= (mConstants.MAX_HISTORY_BUFFER*100)) {
-            throw new ParcelFormatException("File corrupt: history data buffer too large " +
-                    bufSize);
-        } else if ((bufSize&~3) != bufSize) {
-            throw new ParcelFormatException("File corrupt: history data buffer not aligned " +
-                    bufSize);
-        } else {
-            if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
-                    + " bytes at " + curPos);
-            mHistoryBuffer.appendFrom(in, curPos, bufSize);
-            in.setDataPosition(curPos + bufSize);
-        }
-
-        if (DEBUG_HISTORY) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** OLD mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-        mHistoryBaseTimeMs = historyBaseTime;
-        if (DEBUG_HISTORY) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** NEW mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-
-        // We are just arbitrarily going to insert 1 minute from the sample of
-        // the last run until samples in this run.
-        if (mHistoryBaseTimeMs > 0) {
-            long oldnow = mClock.elapsedRealtime();
-            mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
-            if (DEBUG_HISTORY) {
-                StringBuilder sb = new StringBuilder(128);
-                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
-                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-                Slog.i(TAG, sb.toString());
-            }
-        }
-    }
-
-    void writeHistoryBuffer(Parcel out, boolean inclData) {
-        if (DEBUG_HISTORY) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            sb.append(" mLastHistoryElapsedRealtimeMs: ");
-            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-        out.writeInt(BatteryStatsHistory.VERSION);
-        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
-        if (!inclData) {
-            out.writeInt(0);
-            out.writeInt(0);
-            return;
-        }
-
-        out.writeInt(mHistoryBuffer.dataSize());
-        if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
-                + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
-        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
-    }
-
-    @GuardedBy("this")
     public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
         final int version = in.readInt();
 
@@ -15916,31 +15138,7 @@
             return;
         }
 
-        boolean inclHistory = in.readBoolean();
-        if (inclHistory) {
-            readHistoryBuffer(in);
-            mBatteryStatsHistory.readFromParcel(in);
-        }
-
-        mHistoryTagPool.clear();
-        mNextHistoryTagIdx = 0;
-        mNumHistoryTagChars = 0;
-
-        int numTags = in.readInt();
-        for (int i=0; i<numTags; i++) {
-            int idx = in.readInt();
-            String str = in.readString();
-            int uid = in.readInt();
-            HistoryTag tag = new HistoryTag();
-            tag.string = str;
-            tag.uid = uid;
-            tag.poolIdx = idx;
-            mHistoryTagPool.put(tag, idx);
-            if (idx >= mNextHistoryTagIdx) {
-                mNextHistoryTagIdx = idx+1;
-            }
-            mNumHistoryTagChars += tag.string.length() + 1;
-        }
+        mHistory.readSummaryFromParcel(in);
 
         mStartCount = in.readInt();
         mUptimeUs = in.readLong();
@@ -15953,7 +15151,7 @@
         mDischargeUnplugLevel = in.readInt();
         mDischargePlugLevel = in.readInt();
         mDischargeCurrentLevel = in.readInt();
-        mCurrentBatteryLevel = in.readInt();
+        mBatteryLevel = in.readInt();
         mEstimatedBatteryCapacityMah = in.readInt();
         mLastLearnedBatteryCapacityUah = in.readInt();
         mMinLearnedBatteryCapacityUah = in.readInt();
@@ -16456,19 +15654,7 @@
 
         out.writeInt(VERSION);
 
-        out.writeBoolean(inclHistory);
-        if (inclHistory) {
-            writeHistoryBuffer(out, true);
-            mBatteryStatsHistory.writeToParcel(out);
-        }
-
-        out.writeInt(mHistoryTagPool.size());
-        for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
-            HistoryTag tag = ent.getKey();
-            out.writeInt(ent.getValue());
-            out.writeString(tag.string);
-            out.writeInt(tag.uid);
-        }
+        mHistory.writeSummaryToParcel(out, inclHistory);
 
         out.writeInt(mStartCount);
         out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED));
@@ -16481,7 +15667,7 @@
         out.writeInt(mDischargeUnplugLevel);
         out.writeInt(mDischargePlugLevel);
         out.writeInt(mDischargeCurrentLevel);
-        out.writeInt(mCurrentBatteryLevel);
+        out.writeInt(mBatteryLevel);
         out.writeInt(mEstimatedBatteryCapacityMah);
         out.writeInt(mLastLearnedBatteryCapacityUah);
         out.writeInt(mMinLearnedBatteryCapacityUah);
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 0cdd4d1..c36d950 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -22,7 +22,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.Parcel;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UidBatteryConsumer;
@@ -32,10 +31,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.PowerProfile;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -220,18 +217,7 @@
             }
 
             BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-
-            // Make a copy of battery history to avoid concurrent modification.
-            Parcel historyBuffer = Parcel.obtain();
-            historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0,
-                    batteryStatsImpl.mHistoryBuffer.dataSize());
-
-            final File systemDir =
-                    batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile();
-            final BatteryStatsHistory batteryStatsHistory =
-                    new BatteryStatsHistory(historyBuffer, systemDir, null);
-
-            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory);
+            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory());
         }
 
         BatteryUsageStats stats = batteryUsageStatsBuilder.build();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 61a7f38..5c934852 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -28,6 +28,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.Clock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,13 +50,14 @@
     private final Parcel mHistoryBuffer = Parcel.obtain();
     private File mSystemDir;
     private File mHistoryDir;
+    private final Clock mClock = new MockClock();
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         Context context = InstrumentationRegistry.getContext();
         mSystemDir = context.getDataDir();
-        mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR);
+        mHistoryDir = new File(mSystemDir, "battery-history");
         String[] files = mHistoryDir.list();
         if (files != null) {
             for (int i = 0; i < files.length; i++) {
@@ -67,8 +69,8 @@
 
     @Test
     public void testConstruct() {
-        BatteryStatsHistory history =
-                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+        BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+                null, mClock);
         createActiveFile(history);
         verifyFileNumbers(history, Arrays.asList(0));
         verifyActiveFile(history, "0.bin");
@@ -76,8 +78,8 @@
 
     @Test
     public void testStartNextFile() {
-        BatteryStatsHistory history =
-                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+        BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+                null, mClock);
         List<Integer> fileList = new ArrayList<>();
         fileList.add(0);
         createActiveFile(history);
@@ -114,13 +116,13 @@
         assertEquals(0, history.getHistoryUsedSize());
 
         // create a new BatteryStatsHistory object, it will pick up existing history files.
-        BatteryStatsHistory history2 =
-                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
-        // verify construct can pick up all files from file system.
+        BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+                null, mClock);
+        // verify constructor can pick up all files from file system.
         verifyFileNumbers(history2, fileList);
         verifyActiveFile(history2, "33.bin");
 
-        history2.resetAllFiles();
+        history2.reset();
         createActiveFile(history2);
         // verify all existing files are deleted.
         for (int i = 2; i < 33; ++i) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 713e786..570b2ee 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -63,6 +63,7 @@
     MockBatteryStatsImpl(Clock clock, File historyDirectory) {
         super(clock, historyDirectory);
         initTimersAndCounters();
+        setMaxHistoryBuffer(128 * 1024);
 
         setExternalStatsSyncLocked(mExternalStatsSync);
         informThatAllExternalStatsAreFlushed();
@@ -104,12 +105,6 @@
         return mForceOnBattery ? true : super.isOnBattery();
     }
 
-    public void forceRecordAllHistory() {
-        mHaveBatteryLevel = true;
-        mRecordingHistory = true;
-        mRecordAllHistory = true;
-    }
-
     public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
         return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
     }
@@ -201,12 +196,14 @@
     @GuardedBy("this")
     public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
         mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
+        mConstants.onChange();
         return this;
     }
 
     @GuardedBy("this")
     public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
         mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
+        mConstants.onChange();
         return this;
     }