Fix issue @16555033: Battery history overflowing too much

- No longer track process starts/stops normally.
- Increase buffer size to 256KB.
- Buffer size increase requires reworking how battery stats
  are retrieved, since it is going to be hitting IPC limits.
- Also, store the last full stats after a reset, to be reported
  at the next checkin.
- Also, discharge and charge times are tagged with the screen
  and battery save state during that time.

Change-Id: Ie108ac9b626846108a9bb858101ac2b93276ac16
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 176a81c..cc0d569 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -21,7 +21,6 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
-import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
 interface IPackageInstaller {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 545b19a..ba79f91 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -33,6 +33,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.view.Display;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
@@ -1596,6 +1597,27 @@
      */
     public abstract long computeBatteryTimeRemaining(long curTime);
 
+    // The part of a step duration that is the actual time.
+    public static final long STEP_LEVEL_TIME_MASK = 0x000000ffffffffffL;
+
+    // Bits in a step duration that are the new battery level we are at.
+    public static final long STEP_LEVEL_LEVEL_MASK = 0x0000ff0000000000L;
+    public static final long STEP_LEVEL_LEVEL_SHIFT = 40;
+
+    // Bits in a step duration that are the initial mode we were in at that step.
+    public static final long STEP_LEVEL_INITIAL_MODE_MASK = 0x00ff000000000000L;
+    public static final long STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
+
+    // Bits in a step duration that indicate which modes changed during that step.
+    public static final long STEP_LEVEL_MODIFIED_MODE_MASK = 0xff00000000000000L;
+    public static final long STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
+
+    // Step duration mode: the screen is on, off, dozed, etc; value is Display.STATE_* - 1.
+    public static final int STEP_LEVEL_MODE_SCREEN_STATE = 0x03;
+
+    // Step duration mode: power save is on.
+    public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04;
+
     /**
      * Return the historical number of discharge steps we currently have.
      */
@@ -3620,14 +3642,60 @@
         if (!checkin) {
             pw.println(header);
         }
-        String[] lineArgs = new String[1];
+        String[] lineArgs = new String[4];
         for (int i=0; i<count; i++) {
+            long duration = steps[i] & STEP_LEVEL_TIME_MASK;
+            int level = (int)((steps[i] & STEP_LEVEL_LEVEL_MASK)
+                    >> STEP_LEVEL_LEVEL_SHIFT);
+            long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK)
+                    >> STEP_LEVEL_INITIAL_MODE_SHIFT;
+            long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK)
+                    >> STEP_LEVEL_MODIFIED_MODE_SHIFT;
             if (checkin) {
-                lineArgs[0] = Long.toString(steps[i]);
+                lineArgs[0] = Long.toString(duration);
+                lineArgs[1] = Integer.toString(level);
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: lineArgs[2] = "s-"; break;
+                        case Display.STATE_ON: lineArgs[2] = "s+"; break;
+                        case Display.STATE_DOZE: lineArgs[2] = "sd"; break;
+                        case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break;
+                        default: lineArgs[1] = "?"; break;
+                    }
+                } else {
+                    lineArgs[2] = "";
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    lineArgs[3] = (initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0 ? "p+" : "p-";
+                } else {
+                    lineArgs[3] = "";
+                }
                 dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
             } else {
                 pw.print("  #"); pw.print(i); pw.print(": ");
-                TimeUtils.formatDuration(steps[i], pw);
+                TimeUtils.formatDuration(duration, pw);
+                pw.print(" to "); pw.print(level);
+                boolean haveModes = false;
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    pw.print(" (");
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: pw.print("screen-off"); break;
+                        case Display.STATE_ON: pw.print("screen-on"); break;
+                        case Display.STATE_DOZE: pw.print("screen-doze"); break;
+                        case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break;
+                        default: lineArgs[1] = "screen-?"; break;
+                    }
+                    haveModes = true;
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    pw.print(haveModes ? ", " : " (");
+                    pw.print((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0
+                            ? "power-save-on" : "power-save-off");
+                    haveModes = true;
+                }
+                if (haveModes) {
+                    pw.print(")");
+                }
                 pw.println();
             }
         }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c43644a..713fcd8 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -18,7 +18,6 @@
 package android.os;
 
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
 import android.content.pm.UserInfo;
 import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 5655506..40965f0 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.BatteryStatsImpl;
 
+import android.os.ParcelFileDescriptor;
 import android.os.WorkSource;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.SignalStrength;
@@ -37,6 +38,8 @@
     // Remaining methods are only used in Java.
     byte[] getStatistics();
 
+    ParcelFileDescriptor getStatisticsStream();
+
     // Return the computed amount of time remaining on battery, in milliseconds.
     // Returns -1 if nothing could be computed.
     long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
index 445d10a..5a83f33 100644
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ b/core/java/com/android/internal/os/AtomicFile.java
@@ -40,7 +40,7 @@
  * appropriate mutual exclusion invariants whenever it accesses the file.
  * </p>
  */
-public class AtomicFile {
+public final class AtomicFile {
     private final File mBaseName;
     private final File mBackupName;
     
@@ -129,7 +129,16 @@
         } catch (IOException e) {
         }
     }
-    
+
+    public boolean exists() {
+        return mBaseName.exists() || mBackupName.exists();
+    }
+
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
     public FileInputStream openRead() throws FileNotFoundException {
         if (mBackupName.exists()) {
             mBaseName.delete();
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 6c9b4b8..63be2d4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -30,19 +30,26 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.Uid;
 import android.os.Bundle;
+import android.os.MemoryFile;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telephony.SignalStrength;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatterySipper.DrainType;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -55,7 +62,7 @@
  * The caller must initialize this class as soon as activity object is ready to use (for example, in
  * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
  */
-public class BatteryStatsHelper {
+public final class BatteryStatsHelper {
 
     private static final boolean DEBUG = false;
 
@@ -63,6 +70,7 @@
 
     private static BatteryStats sStatsXfer;
     private static Intent sBatteryBroadcastXfer;
+    private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
 
     final private Context mContext;
     final private boolean mCollectBatteryBroadcast;
@@ -117,6 +125,68 @@
         mCollectBatteryBroadcast = collectBatteryBroadcast;
     }
 
+    public void storeStatsHistoryInFile(String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(mContext, fname);
+            sFileXfer.put(path, this.getStats());
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(path);
+                Parcel hist = Parcel.obtain();
+                getStats().writeToParcelWithoutUids(hist, 0);
+                byte[] histData = hist.marshall();
+                fout.write(histData);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to write history to file", e);
+            } finally {
+                if (fout != null) {
+                    try {
+                        fout.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    public static BatteryStats statsFromFile(Context context, String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(context, fname);
+            BatteryStats stats = sFileXfer.get(path);
+            if (stats != null) {
+                return stats;
+            }
+            FileInputStream fin = null;
+            try {
+                fin = new FileInputStream(path);
+                byte[] data = readFully(fin);
+                Parcel parcel = Parcel.obtain();
+                parcel.unmarshall(data, 0, data.length);
+                parcel.setDataPosition(0);
+                return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to read history to file", e);
+            } finally {
+                if (fin != null) {
+                    try {
+                        fin.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        return getStats(IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+    }
+
+    public static void dropFile(Context context, String fname) {
+        makeFilePath(context, fname).delete();
+    }
+
+    private static File makeFilePath(Context context, String fname) {
+        return new File(context.getFilesDir(), fname);
+    }
+
     /** Clears the current stats and forces recreating for future use. */
     public void clearStats() {
         mStats = null;
@@ -853,25 +923,64 @@
 
     public long getChargeTimeRemaining() { return mChargeTimeRemaining; }
 
+    public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        return readFully(stream, stream.available());
+    }
+
+    public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
+        int pos = 0;
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length-pos);
+            //Log.i("foo", "Read " + amt + " bytes at " + pos
+            //        + " of avail " + data.length);
+            if (amt <= 0) {
+                //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                //        + " len=" + data.length);
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length-pos) {
+                byte[] newData = new byte[pos+avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
     private void load() {
         if (mBatteryInfo == null) {
             return;
         }
-        try {
-            byte[] data = mBatteryInfo.getStatistics();
-            Parcel parcel = Parcel.obtain();
-            parcel.unmarshall(data, 0, data.length);
-            parcel.setDataPosition(0);
-            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
-                    .createFromParcel(parcel);
-            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
-            mStats = stats;
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException:", e);
-        }
+        mStats = getStats(mBatteryInfo);
         if (mCollectBatteryBroadcast) {
             mBatteryBroadcast = mContext.registerReceiver(null,
                     new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         }
     }
+
+    private static BatteryStatsImpl getStats(IBatteryStats service) {
+        try {
+            ParcelFileDescriptor pfd = service.getStatisticsStream();
+            if (pfd != null) {
+                FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                try {
+                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
+                    Parcel parcel = Parcel.obtain();
+                    parcel.unmarshall(data, 0, data.length);
+                    parcel.setDataPosition(0);
+                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+                            .createFromParcel(parcel);
+                    stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
+                    return stats;
+                } catch (IOException e) {
+                    Log.w(TAG, "Unable to read statistics stream", e);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException:", e);
+        }
+        return null;
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aad1156..50b86d0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -109,6 +109,7 @@
     private static int sNumSpeedSteps;
 
     private final JournaledFile mFile;
+    public final AtomicFile mCheckinFile;
 
     static final int MSG_UPDATE_WAKELOCKS = 1;
     static final int MSG_REPORT_POWER_CHANGE = 2;
@@ -142,7 +143,7 @@
         }
     }
 
-    private final MyHandler mHandler;
+    public final MyHandler mHandler;
 
     private BatteryCallback mCallback;
 
@@ -199,8 +200,8 @@
     boolean mRecordingHistory = false;
     int mNumHistoryItems;
 
-    static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
-    static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+    static final int MAX_HISTORY_BUFFER = 256*1024; // 256KB
+    static final int MAX_MAX_HISTORY_BUFFER = 320*1024; // 320KB
     final Parcel mHistoryBuffer = Parcel.obtain();
     final HistoryItem mHistoryLastWritten = new HistoryItem();
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
@@ -240,7 +241,7 @@
 
     int mWakeLockNesting;
     boolean mWakeLockImportant;
-    boolean mRecordAllWakeLocks;
+    boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
     int mScreenState = Display.STATE_UNKNOWN;
@@ -342,6 +343,10 @@
 
     static final int MAX_LEVEL_STEPS = 100;
 
+    int mInitStepMode = 0;
+    int mCurStepMode = 0;
+    int mModStepMode = 0;
+
     int mLastDischargeStepLevel;
     long mLastDischargeStepTime;
     int mMinDischargeStepLevel;
@@ -429,10 +434,11 @@
     @GuardedBy("this")
     private String[] mWifiIfaces = new String[0];
 
-    // For debugging
     public BatteryStatsImpl() {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
+        clearHistoryLocked();
     }
 
     public static interface TimeBaseObs {
@@ -2353,6 +2359,9 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_START, name, uid, 0)) {
             return;
         }
+        if (!mRecordAllHistory) {
+            return;
+        }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_START, name, uid);
@@ -2371,9 +2380,12 @@
         }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
-        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
         getUidStatsLocked(uid).updateProcessStateLocked(name, Uid.PROCESS_STATE_NONE,
                 elapsedRealtime);
+        if (!mRecordAllHistory) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
     }
 
     public void noteSyncStartLocked(String name, int uid) {
@@ -2427,11 +2439,41 @@
         }
     }
 
-    public void setRecordAllWakeLocksLocked(boolean enabled) {
-        mRecordAllWakeLocks = enabled;
+    public void setRecordAllHistoryLocked(boolean enabled) {
+        mRecordAllHistory = enabled;
         if (!enabled) {
             // Clear out any existing state.
             mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK);
+            // Record the currently running processes as stopping, now that we are no
+            // longer tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                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_FINISH, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
+        } else {
+            // Record the currently running processes as starting, now that we are tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                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));
+                    }
+                }
+            }
         }
     }
 
@@ -2452,7 +2494,7 @@
             if (historyName == null) {
                 historyName = name;
             }
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
                         uid, 0)) {
                     addHistoryEventLocked(elapsedRealtime, uptime,
@@ -2496,7 +2538,7 @@
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (historyName == null) {
                     historyName = name;
                 }
@@ -2788,6 +2830,18 @@
             if (DEBUG) Slog.v(TAG, "Screen state: oldState=" + Display.stateToString(oldState)
                     + ", newState=" + Display.stateToString(state));
 
+            if (state != Display.STATE_UNKNOWN) {
+                int stepState = state-1;
+                if (stepState < 4) {
+                    mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_SCREEN_STATE) ^ stepState;
+                    mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_SCREEN_STATE) | stepState;
+                } else {
+                    Slog.wtf(TAG, "Unexpected screen state: " + state);
+                }
+            }
+
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
             if (state == Display.STATE_ON) {
                 // Screen turning on.
                 final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2924,6 +2978,9 @@
 
     public void noteLowPowerMode(boolean enabled) {
         if (mLowPowerModeEnabled != enabled) {
+            int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
+            mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
+            mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
             mLowPowerModeEnabled = enabled;
@@ -6032,8 +6089,14 @@
         }
     }
 
-    public BatteryStatsImpl(String filename, Handler handler) {
-        mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+    public BatteryStatsImpl(File systemDir, Handler handler) {
+        if (systemDir != null) {
+            mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
+                    new File(systemDir, "batterystats.bin.tmp"));
+        } else {
+            mFile = null;
+        }
+        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6095,6 +6158,7 @@
 
     public BatteryStatsImpl(Parcel p) {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
         clearHistoryLocked();
         readFromParcel(p);
@@ -6390,6 +6454,10 @@
 
     private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
         for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+            if (!mRecordAllHistory && i == HistoryItem.EVENT_PROC) {
+                // Not recording process starts/stops.
+                continue;
+            }
             HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i);
             if (active == null) {
                 continue;
@@ -6455,7 +6523,37 @@
             boolean reset = false;
             if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                     || level >= 90
-                    || (mDischargeCurrentLevel < 20 && level >= 80))) {
+                    || getLowDischargeAmountSinceCharge() >= 60)
+                    || (getHighDischargeAmountSinceCharge() >= 60
+                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER)) {
+                // Before we write, collect a snapshot of the final aggregated
+                // stats to be reported in the next checkin.  Only do this if we have
+                // a sufficient amount of data to make it interesting.
+                if (getLowDischargeAmountSinceCharge() >= 20) {
+                    final Parcel parcel = Parcel.obtain();
+                    writeSummaryToParcel(parcel, true);
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override public void run() {
+                            synchronized (mCheckinFile) {
+                                FileOutputStream stream = null;
+                                try {
+                                    stream = mCheckinFile.startWrite();
+                                    stream.write(parcel.marshall());
+                                    stream.flush();
+                                    FileUtils.sync(stream);
+                                    stream.close();
+                                    mCheckinFile.finishWrite(stream);
+                                } catch (IOException e) {
+                                    Slog.w("BatteryStats",
+                                            "Error writing checkin battery statistics", e);
+                                    mCheckinFile.failWrite(stream);
+                                } finally {
+                                    parcel.recycle();
+                                }
+                            }
+                        }
+                    });
+                }
                 doWrite = true;
                 resetAllStatsLocked();
                 mDischargeStartLevel = level;
@@ -6465,6 +6563,8 @@
             mLastDischargeStepLevel = level;
             mMinDischargeStepLevel = level;
             mLastDischargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
             pullPendingStateUpdatesLocked();
             mHistoryCur.batteryLevel = (byte)level;
             mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
@@ -6504,6 +6604,8 @@
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
             mLastChargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
         }
         if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
             if (mFile != null) {
@@ -6529,14 +6631,17 @@
     private static final int BATTERY_PLUGGED_NONE = 0;
 
     private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
-            int numStepLevels, long elapsedRealtime) {
+            int numStepLevels, long modeBits, long elapsedRealtime) {
         if (lastStepTime >= 0 && numStepLevels > 0) {
             long duration = elapsedRealtime - lastStepTime;
             for (int i=0; i<numStepLevels; i++) {
                 System.arraycopy(steps, 0, steps, 1, steps.length-1);
                 long thisDuration = duration / (numStepLevels-i);
                 duration -= thisDuration;
-                steps[0] = thisDuration;
+                if (thisDuration > STEP_LEVEL_TIME_MASK) {
+                    thisDuration = STEP_LEVEL_TIME_MASK;
+                }
+                steps[0] = thisDuration | modeBits;
             }
             stepCount += numStepLevels;
             if (stepCount > steps.length) {
@@ -6623,23 +6728,30 @@
                 if (changed) {
                     addHistoryRecordLocked(elapsedRealtime, uptime);
                 }
+                long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+                        | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+                        | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
                 if (onBattery) {
                     if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
                         mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
                                 mNumDischargeStepDurations, mLastDischargeStepTime,
-                                mLastDischargeStepLevel - level, elapsedRealtime);
+                                mLastDischargeStepLevel - level, modeBits, elapsedRealtime);
                         mLastDischargeStepLevel = level;
                         mMinDischargeStepLevel = level;
                         mLastDischargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 } else {
                     if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
                         mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
                                 mNumChargeStepDurations, mLastChargeStepTime,
-                                level - mLastChargeStepLevel, elapsedRealtime);
+                                level - mLastChargeStepLevel, modeBits, elapsedRealtime);
                         mLastChargeStepLevel = level;
                         mMaxChargeStepLevel = level;
                         mLastChargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 }
             }
@@ -6863,7 +6975,7 @@
         }
         long total = 0;
         for (int i=0; i<numSteps; i++) {
-            total += steps[i];
+            total += steps[i] & STEP_LEVEL_TIME_MASK;
         }
         return total / numSteps;
         /*
@@ -6875,7 +6987,7 @@
             long totalTime = 0;
             int num = 0;
             for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
-                totalTime += steps[i+j];
+                totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
                 num++;
             }
             buckets[numBuckets] = totalTime / num;
@@ -7213,7 +7325,7 @@
         }
 
         Parcel out = Parcel.obtain();
-        writeSummaryToParcel(out);
+        writeSummaryToParcel(out, true);
         mLastWriteTime = SystemClock.elapsedRealtime();
 
         if (mPendingWrite != null) {
@@ -7224,14 +7336,11 @@
         if (sync) {
             commitPendingDataToDisk();
         } else {
-            Thread thr = new Thread("BatteryStats-Write") {
-                @Override
-                public void run() {
-                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            BackgroundThread.getHandler().post(new Runnable() {
+                @Override public void run() {
                     commitPendingDataToDisk();
                 }
-            };
-            thr.start();
+            });
         }
     }
 
@@ -7263,29 +7372,6 @@
         }
     }
 
-    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
-        int pos = 0;
-        int avail = stream.available();
-        byte[] data = new byte[avail];
-        while (true) {
-            int amt = stream.read(data, pos, data.length-pos);
-            //Log.i("foo", "Read " + amt + " bytes at " + pos
-            //        + " of avail " + data.length);
-            if (amt <= 0) {
-                //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                //        + " len=" + data.length);
-                return data;
-            }
-            pos += amt;
-            avail = stream.available();
-            if (avail > data.length-pos) {
-                byte[] newData = new byte[pos+avail];
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
     public void readLocked() {
         if (mFile == null) {
             Slog.w("BatteryStats", "readLocked: no file associated with this instance");
@@ -7301,7 +7387,7 @@
             }
             FileInputStream stream = new FileInputStream(file);
 
-            byte[] raw = readFully(stream);
+            byte[] raw = BatteryStatsHelper.readFully(stream);
             Parcel in = Parcel.obtain();
             in.unmarshall(raw, 0, raw.length);
             in.setDataPosition(0);
@@ -7410,7 +7496,7 @@
         }
     }
 
-    void writeHistory(Parcel out, boolean andOldHistory) {
+    void writeHistory(Parcel out, boolean inclData, boolean andOldHistory) {
         if (DEBUG_HISTORY) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("****************** WRITING mHistoryBaseTime: ");
@@ -7420,6 +7506,11 @@
             Slog.i(TAG, sb.toString());
         }
         out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime);
+        if (!inclData) {
+            out.writeInt(0);
+            out.writeInt(0);
+            return;
+        }
         out.writeInt(mHistoryTagPool.size());
         for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
             HistoryTag tag = ent.getKey();
@@ -7449,7 +7540,7 @@
         out.writeLong(-1);
     }
 
-    private void readSummaryFromParcel(Parcel in) {
+    public void readSummaryFromParcel(Parcel in) {
         final int version = in.readInt();
         if (version != VERSION) {
             Slog.w("BatteryStats", "readFromParcel: version got " + version
@@ -7742,7 +7833,7 @@
      *
      * @param out the Parcel to be written to.
      */
-    public void writeSummaryToParcel(Parcel out) {
+    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
         pullPendingStateUpdatesLocked();
 
         final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
@@ -7750,7 +7841,7 @@
 
         out.writeInt(VERSION);
 
-        writeHistory(out, true);
+        writeHistory(out, inclHistory, true);
 
         out.writeInt(mStartCount);
         out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
@@ -8194,7 +8285,7 @@
 
         out.writeInt(MAGIC);
 
-        writeHistory(out, false);
+        writeHistory(out, true, false);
 
         out.writeInt(mStartCount);
         out.writeLong(mStartClockTime);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e0d4aad..b6f555d 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2200,8 +2200,7 @@
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         systemDir.mkdirs();
-        mBatteryStatsService = new BatteryStatsService(new File(
-                systemDir, "batterystats.bin").toString(), mHandler);
+        mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
         mOnBattery = DEBUG_POWER ? true
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 3fdeb54..d8da700 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -42,7 +43,10 @@
 import com.android.internal.os.PowerProfile;
 import com.android.server.LocalServices;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -62,8 +66,8 @@
     private BluetoothHeadset mBluetoothHeadset;
     PowerManagerInternal mPowerManagerInternal;
 
-    BatteryStatsService(String filename, Handler handler) {
-        mStats = new BatteryStatsImpl(filename, handler);
+    BatteryStatsService(File systemDir, Handler handler) {
+        mStats = new BatteryStatsImpl(systemDir, handler);
     }
     
     public void publish(Context context) {
@@ -117,7 +121,7 @@
     public BatteryStatsImpl getActiveStatistics() {
         return mStats;
     }
-    
+
     public byte[] getStatistics() {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -129,7 +133,24 @@
         out.recycle();
         return data;
     }
-    
+
+    public ParcelFileDescriptor getStatisticsStream() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        //Slog.i("foo", "SENDING BATTERY INFO:");
+        //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
+        Parcel out = Parcel.obtain();
+        mStats.writeToParcel(out, 0);
+        byte[] data = out.marshall();
+        out.recycle();
+        try {
+            return ParcelFileDescriptor.fromData(data, "battery-stats");
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to create shared memory", e);
+            return null;
+        }
+    }
+
     public long computeBatteryTimeRemaining() {
         synchronized (mStats) {
             long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
@@ -726,7 +747,8 @@
         pw.println("  enable|disable <option>");
         pw.println("    Enable or disable a running option.  Option state is not saved across boots.");
         pw.println("    Options are:");
-        pw.println("      full-wake-history: include wake_lock_in battery history, full wake details.");
+        pw.println("      full-history: include additional detailed events in battery history:");
+        pw.println("          wake_lock_in and proc events");
         pw.println("      no-auto-reset: don't automatically reset stats when unplugged");
     }
 
@@ -737,9 +759,9 @@
             dumpHelp(pw);
             return -1;
         }
-        if ("full-wake-history".equals(args[i])) {
+        if ("full-wake-history".equals(args[i]) || "full-history".equals(args[i])) {
             synchronized (mStats) {
-                mStats.setRecordAllWakeLocksLocked(enable);
+                mStats.setRecordAllHistoryLocked(enable);
             }
         } else if ("no-auto-reset".equals(args[i])) {
             synchronized (mStats) {
@@ -764,7 +786,8 @@
         }
 
         int flags = 0;
-        boolean isCheckin = false;
+        boolean useCheckinFormat = false;
+        boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
         long historyStart = -1;
@@ -773,7 +796,8 @@
             for (int i=0; i<args.length; i++) {
                 String arg = args[i];
                 if ("--checkin".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
+                    isRealCheckin = true;
                 } else if ("--history".equals(arg)) {
                     flags |= BatteryStats.DUMP_HISTORY_ONLY;
                 } else if ("--history-start".equals(arg)) {
@@ -787,7 +811,7 @@
                     historyStart = Long.parseLong(args[i]);
                     writeData = true;
                 } else if ("-c".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
                 } else if ("--unplugged".equals(arg)) {
                     flags |= BatteryStats.DUMP_UNPLUGGED_ONLY;
@@ -844,8 +868,35 @@
         if (noOutput) {
             return;
         }
-        if (isCheckin) {
+        if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
             synchronized (mStats) {
                 mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart);
                 if (writeData) {