More battery history improvements.

- Better batching of history items.  Fixed problems where empty
  entries would be created because state toggles got lost.
- The string pool is now a HistoryTag pool, containing both a string
  and uid; now an entry only requires 16 bits in the history data.
- Acquiring the first wake lock also now includes a HistoryTag
  identifying who did the aquisition.
- Cleaned up printing of signal strengths and cell radio types.
- There was code that tried to allow you to add new history entries
  while iterating the history...  but these should never happen
  together, so turned that into a failure...  and fixed an issue
  where we could leave the battery stats in a state where it
  thinks it is continually iterating.

Change-Id: I1afa57ee2d66b186932c502dbdd633cdd4aed353
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2afea1f..27c0f5d 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -452,6 +452,56 @@
         }
     }
 
+    public final static class HistoryTag {
+        public String string;
+        public int uid;
+
+        public int poolIdx;
+
+        public void setTo(HistoryTag o) {
+            string = o.string;
+            uid = o.uid;
+            poolIdx = o.poolIdx;
+        }
+
+        public void setTo(String _string, int _uid) {
+            string = _string;
+            uid = _uid;
+            poolIdx = -1;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(string);
+            dest.writeInt(uid);
+        }
+
+        public void readFromParcel(Parcel src) {
+            string = src.readString();
+            uid = src.readInt();
+            poolIdx = -1;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            HistoryTag that = (HistoryTag) o;
+
+            if (uid != that.uid) return false;
+            if (!string.equals(that.string)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = string.hashCode();
+            result = 31 * result + uid;
+            return result;
+        }
+    }
+
     public final static class HistoryItem implements Parcelable {
         public HistoryItem next;
         
@@ -500,7 +550,7 @@
         public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT;
         // Constants from DATA_CONNECTION_*
         public static final int STATE_DATA_CONNECTION_SHIFT = 9;
-        public static final int STATE_DATA_CONNECTION_MASK = 0x1f;
+        public static final int STATE_DATA_CONNECTION_MASK = 0x1f << STATE_DATA_CONNECTION_SHIFT;
 
         // These states always appear directly in the first int token
         // of a delta change; they should be ones that change relatively
@@ -529,21 +579,30 @@
 
         public int states;
 
+        // The wake lock that was acquired at this point.
+        public HistoryTag wakelockTag;
+
         public static final int EVENT_NONE = 0;
         public static final int EVENT_PROC_STARTED = 1;
         public static final int EVENT_PROC_FINISHED = 2;
 
         // For CMD_EVENT.
         public int eventCode;
-        public String eventName;
-        public int eventNameIdx;    // only filled in when iterating.
-        public int eventUid;
+        public HistoryTag eventTag;
+
+        // Meta-data when reading.
+        public int numReadInts;
+
+        // Pre-allocated objects.
+        public final HistoryTag localWakelockTag = new HistoryTag();
+        public final HistoryTag localEventTag = new HistoryTag();
 
         public HistoryItem() {
         }
         
         public HistoryItem(long time, Parcel src) {
             this.time = time;
+            numReadInts = 2;
             readFromParcel(src);
         }
         
@@ -563,14 +622,20 @@
                     | ((((int)batteryVoltage)<<16)&0xffff0000);
             dest.writeInt(bat);
             dest.writeInt(states);
+            if (wakelockTag != null) {
+                dest.writeInt(1);
+                wakelockTag.writeToParcel(dest, flags);
+            } else {
+                dest.writeInt(0);
+            }
             if (cmd == CMD_EVENT) {
                 dest.writeInt(eventCode);
-                dest.writeInt(eventUid);
-                dest.writeString(eventName);
+                eventTag.writeToParcel(dest, flags);
             }
         }
 
         public void readFromParcel(Parcel src) {
+            int start = src.dataPosition();
             int bat = src.readInt();
             cmd = (byte)(bat&0xff);
             batteryLevel = (byte)((bat>>8)&0xff);
@@ -581,14 +646,21 @@
             batteryTemperature = (short)(bat&0xffff);
             batteryVoltage = (char)((bat>>16)&0xffff);
             states = src.readInt();
+            if (src.readInt() != 0) {
+                wakelockTag = localWakelockTag;
+                wakelockTag.readFromParcel(src);
+            } else {
+                wakelockTag = null;
+            }
             if (cmd == CMD_EVENT) {
                 eventCode = src.readInt();
-                eventUid = src.readInt();
-                eventName = src.readString();
-                eventNameIdx = 0;
+                eventTag = localEventTag;
+                eventTag.readFromParcel(src);
             } else {
                 eventCode = EVENT_NONE;
+                eventTag = null;
             }
+            numReadInts += (src.dataPosition()-start)/4;
         }
 
         public void clear() {
@@ -601,9 +673,9 @@
             batteryTemperature = 0;
             batteryVoltage = 0;
             states = 0;
+            wakelockTag = null;
             eventCode = EVENT_NONE;
-            eventUid = 0;
-            eventName = null;
+            eventTag = null;
         }
         
         public void setTo(HistoryItem o) {
@@ -616,10 +688,19 @@
             batteryTemperature = o.batteryTemperature;
             batteryVoltage = o.batteryVoltage;
             states = o.states;
+            if (o.wakelockTag != null) {
+                wakelockTag = localWakelockTag;
+                wakelockTag.setTo(o.wakelockTag);
+            } else {
+                wakelockTag = null;
+            }
             eventCode = o.eventCode;
-            eventUid = o.eventUid;
-            eventName = o.eventName;
-            eventNameIdx = o.eventNameIdx;
+            if (o.eventTag != null) {
+                eventTag = localEventTag;
+                eventTag.setTo(o.eventTag);
+            } else {
+                eventTag = null;
+            }
         }
 
         public void setTo(long time, byte cmd, int eventCode, int eventUid, String eventName,
@@ -627,9 +708,12 @@
             this.time = time;
             this.cmd = cmd;
             this.eventCode = eventCode;
-            this.eventUid = eventUid;
-            this.eventName = eventName;
-            this.eventNameIdx = 0;
+            if (eventCode != EVENT_NONE) {
+                eventTag = localEventTag;
+                eventTag.setTo(eventName, eventUid);
+            } else {
+                eventTag = null;
+            }
             batteryLevel = o.batteryLevel;
             batteryStatus = o.batteryStatus;
             batteryHealth = o.batteryHealth;
@@ -637,6 +721,12 @@
             batteryTemperature = o.batteryTemperature;
             batteryVoltage = o.batteryVoltage;
             states = o.states;
+            if (o.wakelockTag != null) {
+                wakelockTag = localWakelockTag;
+                wakelockTag.setTo(o.wakelockTag);
+            } else {
+                wakelockTag = null;
+            }
         }
 
         public boolean sameNonEvent(HistoryItem o) {
@@ -650,13 +740,26 @@
         }
 
         public boolean same(HistoryItem o) {
-            if (!sameNonEvent(o) || eventCode != o.eventCode || eventUid != o.eventUid) {
+            if (!sameNonEvent(o) || eventCode != o.eventCode) {
                 return false;
             }
-            if (eventName == o.eventName) {
-                return true;
+            if (wakelockTag != o.wakelockTag) {
+                if (wakelockTag == null || o.wakelockTag == null) {
+                    return false;
+                }
+                if (!wakelockTag.equals(o.wakelockTag)) {
+                    return false;
+                }
             }
-            return eventName != null && o.eventName != null && eventName.equals(o.eventName);
+            if (eventTag != o.eventTag) {
+                if (eventTag == null || o.eventTag == null) {
+                    return false;
+                }
+                if (!eventTag.equals(o.eventTag)) {
+                    return false;
+                }
+            }
+            return true;
         }
     }
     
@@ -688,11 +791,19 @@
         }
     }
     
+    public abstract int getHistoryTotalSize();
+
+    public abstract int getHistoryUsedSize();
+
     public abstract boolean startIteratingHistoryLocked();
 
     public abstract int getHistoryStringPoolSize();
 
-    public abstract String getHistoryStringPoolItem(int index);
+    public abstract int getHistoryStringPoolBytes();
+
+    public abstract String getHistoryTagPoolString(int index);
+
+    public abstract int getHistoryTagPoolUid(int index);
 
     public abstract boolean getNextHistoryLocked(HistoryItem out);
 
@@ -1746,14 +1857,14 @@
         
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Signal levels: ");
+        sb.append("  Signal levels:");
         didOne = false;
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             final long time = getPhoneSignalStrengthTime(i, batteryRealtime, which);
             if (time == 0) {
                 continue;
             }
-            if (didOne) sb.append(", ");
+            sb.append("\n    ");
             didOne = true;
             sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
             sb.append(" ");
@@ -1764,7 +1875,7 @@
             sb.append(getPhoneSignalStrengthCount(i, which));
             sb.append("x");
         }
-        if (!didOne) sb.append("No activity");
+        if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
@@ -1775,14 +1886,14 @@
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Radio types: ");
+        sb.append("  Radio types:");
         didOne = false;
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
             final long time = getPhoneDataConnectionTime(i, batteryRealtime, which);
             if (time == 0) {
                 continue;
             }
-            if (didOne) sb.append(", ");
+            sb.append("\n    ");
             didOne = true;
             sb.append(DATA_CONNECTION_NAMES[i]);
             sb.append(" ");
@@ -1793,7 +1904,7 @@
             sb.append(getPhoneDataConnectionCount(i, which));
             sb.append("x");
         }
-        if (!didOne) sb.append("No activity");
+        if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
@@ -2259,21 +2370,30 @@
         }
     }
 
-    static void printBitDescriptions(PrintWriter pw, int oldval, int newval,
+    static void printBitDescriptions(PrintWriter pw, int oldval, int newval, HistoryTag wakelockTag,
             BitDescription[] descriptions, boolean longNames) {
         int diff = oldval ^ newval;
         if (diff == 0) return;
+        boolean didWake = false;
         for (int i=0; i<descriptions.length; i++) {
             BitDescription bd = descriptions[i];
-            int mask = bd.mask;
-            if (bd.shift > 0) {
-                mask <<= bd.shift;
-            }
-            if ((diff&mask) != 0) {
+            if ((diff&bd.mask) != 0) {
                 pw.print(longNames ? " " : ",");
                 if (bd.shift < 0) {
-                    pw.print((newval&mask) != 0 ? "+" : "-");
+                    pw.print((newval&bd.mask) != 0 ? "+" : "-");
                     pw.print(longNames ? bd.name : bd.shortName);
+                    if (bd.mask == HistoryItem.STATE_WAKE_LOCK_FLAG && wakelockTag != null) {
+                        didWake = true;
+                        pw.print("=");
+                        if (longNames) {
+                            UserHandle.formatUid(pw, wakelockTag.uid);
+                            pw.print(":\"");
+                            pw.print(wakelockTag.string);
+                            pw.print("\"");
+                        } else {
+                            pw.print(wakelockTag.poolIdx);
+                        }
+                    }
                 } else {
                     pw.print(longNames ? bd.name : bd.shortName);
                     pw.print("=");
@@ -2286,6 +2406,17 @@
                 }
             }
         }
+        if (!didWake && wakelockTag != null) {
+            pw.print(longNames ? "wake_lock=" : "w=");
+            if (longNames) {
+                UserHandle.formatUid(pw, wakelockTag.uid);
+                pw.print(":\"");
+                pw.print(wakelockTag.string);
+                pw.print("\"");
+            } else {
+                pw.print(wakelockTag.poolIdx);
+            }
+        }
     }
     
     public void prepareForDumpLocked() {
@@ -2305,7 +2436,9 @@
             if (!checkin) {
                 pw.print("  ");
                 TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
-                pw.print(" ");
+                pw.print(" (");
+                pw.print(rec.numReadInts);
+                pw.print(") ");
             } else {
                 if (lastTime < 0) {
                     pw.print("@");
@@ -2427,7 +2560,7 @@
                     pw.print(checkin ? ",Bv=" : " volt=");
                     pw.print(oldVolt);
                 }
-                printBitDescriptions(pw, oldState, rec.states,
+                printBitDescriptions(pw, oldState, rec.states, rec.wakelockTag,
                         HISTORY_STATE_DESCRIPTIONS, !checkin);
                 if (rec.eventCode != HistoryItem.EVENT_NONE) {
                     switch (rec.eventCode) {
@@ -2450,15 +2583,12 @@
                             break;
                     }
                     if (checkin) {
-                        pw.print(rec.eventUid);
+                        pw.print(rec.eventTag.poolIdx);
                     } else {
-                        UserHandle.formatUid(pw, rec.eventUid);
-                    }
-                    pw.print(":");
-                    if (checkin) {
-                        pw.print(rec.eventNameIdx);
-                    } else {
-                        pw.print(rec.eventName);
+                        UserHandle.formatUid(pw, rec.eventTag.uid);
+                        pw.print(":\"");
+                        pw.print(rec.eventTag.string);
+                        pw.print("\"");
                     }
                 }
                 pw.println();
@@ -2467,6 +2597,33 @@
         }
     }
 
+    private void printSizeValue(PrintWriter pw, long size) {
+        float result = size;
+        String suffix = "";
+        if (result >= 10*1024) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        pw.print((int)result);
+        pw.print(suffix);
+    }
+
     /**
      * Dumps a human-readable summary of the battery statistics to the given PrintWriter.
      *
@@ -2480,24 +2637,42 @@
         long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
 
         final HistoryItem rec = new HistoryItem();
+        final long historyTotalSize = getHistoryTotalSize();
+        final long historyUsedSize = getHistoryUsedSize();
         if (startIteratingHistoryLocked()) {
-            pw.println("Battery History:");
-            HistoryPrinter hprinter = new HistoryPrinter();
-            while (getNextHistoryLocked(rec)) {
-                hprinter.printNextItem(pw, rec, now, false);
+            try {
+                pw.print("Battery History (");
+                pw.print((100*historyUsedSize)/historyTotalSize);
+                pw.print("% used, ");
+                printSizeValue(pw, historyUsedSize);
+                pw.print(" used of ");
+                printSizeValue(pw, historyTotalSize);
+                pw.print(", ");
+                pw.print(getHistoryStringPoolSize());
+                pw.print(" strings using ");
+                printSizeValue(pw, getHistoryStringPoolBytes());
+                pw.println("):");
+                HistoryPrinter hprinter = new HistoryPrinter();
+                while (getNextHistoryLocked(rec)) {
+                    hprinter.printNextItem(pw, rec, now, false);
+                }
+                pw.println();
+            } finally {
+                finishIteratingHistoryLocked();
             }
-            finishIteratingHistoryLocked();
-            pw.println("");
         }
 
         if (startIteratingOldHistoryLocked()) {
-            pw.println("Old battery History:");
-            HistoryPrinter hprinter = new HistoryPrinter();
-            while (getNextOldHistoryLocked(rec)) {
-                hprinter.printNextItem(pw, rec, now, false);
+            try {
+                pw.println("Old battery History:");
+                HistoryPrinter hprinter = new HistoryPrinter();
+                while (getNextOldHistoryLocked(rec)) {
+                    hprinter.printNextItem(pw, rec, now, false);
+                }
+                pw.println();
+            } finally {
+                finishIteratingOldHistoryLocked();
             }
-            finishIteratingOldHistoryLocked();
-            pw.println("");
         }
 
         if (historyOnly) {
@@ -2553,21 +2728,26 @@
         if (includeHistory || historyOnly) {
             final HistoryItem rec = new HistoryItem();
             if (startIteratingHistoryLocked()) {
-                for (int i=0; i<getHistoryStringPoolSize(); i++) {
-                    pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
-                    pw.print(HISTORY_STRING_POOL); pw.print(',');
-                    pw.print(i);
-                    pw.print(',');
-                    pw.print(getHistoryStringPoolItem(i));
-                    pw.println();
+                try {
+                    for (int i=0; i<getHistoryStringPoolSize(); i++) {
+                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+                        pw.print(HISTORY_STRING_POOL); pw.print(',');
+                        pw.print(i);
+                        pw.print(',');
+                        pw.print(getHistoryTagPoolString(i));
+                        pw.print(',');
+                        pw.print(getHistoryTagPoolUid(i));
+                        pw.println();
+                    }
+                    HistoryPrinter hprinter = new HistoryPrinter();
+                    while (getNextHistoryLocked(rec)) {
+                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+                        pw.print(HISTORY_DATA); pw.print(',');
+                        hprinter.printNextItem(pw, rec, now, true);
+                    }
+                } finally {
+                    finishIteratingHistoryLocked();
                 }
-                HistoryPrinter hprinter = new HistoryPrinter();
-                while (getNextHistoryLocked(rec)) {
-                    pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
-                    pw.print(HISTORY_DATA); pw.print(',');
-                    hprinter.printNextItem(pw, rec, now, true);
-                }
-                finishIteratingHistoryLocked();
             }
         }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7425445..68c41fa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -87,7 +87,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 71 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 75 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -189,9 +189,12 @@
     final HistoryItem mHistoryLastWritten = new HistoryItem();
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
     final HistoryItem mHistoryReadTmp = new HistoryItem();
-    final HashMap<String, Integer> mHistoryStringPool = new HashMap<String, Integer>();
+    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>();
     String[] mReadHistoryStrings;
-    int mNextHistoryStringIdx = 0;
+    int[] mReadHistoryUids;
+    int mReadHistoryChars;
+    int mNextHistoryTagIdx = 0;
+    int mNumHistoryTagChars = 0;
     int mHistoryBufferLastPos = -1;
     boolean mHistoryOverflow = false;
     long mLastHistoryTime = 0;
@@ -1486,18 +1489,43 @@
         mBtHeadset = headset;
     }
 
+    private int writeHistoryTag(HistoryTag tag) {
+        Integer idxObj = mHistoryTagPool.get(tag);
+        int idx;
+        if (idxObj != null) {
+            idx = idxObj;
+        } else {
+            idx = mNextHistoryTagIdx;
+            HistoryTag key = new HistoryTag();
+            key.setTo(tag);
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(key, idx);
+            mNextHistoryTagIdx++;
+            mNumHistoryTagChars += key.string.length() + 1;
+        }
+        return idx;
+    }
+
+    private void readHistoryTag(int index, HistoryTag tag) {
+        tag.string = mReadHistoryStrings[index];
+        tag.uid = mReadHistoryUids[index];
+        tag.poolIdx = index;
+    }
+
     // Part of initial delta int that specifies the time delta.
-    static final int DELTA_TIME_MASK = 0x3ffff;
-    static final int DELTA_TIME_ABS = 0x3fffd;    // Following is an entire abs update.
-    static final int DELTA_TIME_INT = 0x3fffe;    // The delta is a following int
-    static final int DELTA_TIME_LONG = 0x3ffff;   // The delta is a following long
+    static final int DELTA_TIME_MASK = 0x1ffff;
+    static final int DELTA_TIME_LONG = 0x1ffff;   // The delta is a following long
+    static final int DELTA_TIME_INT = 0x1fffe;    // The delta is a following int
+    static final int DELTA_TIME_ABS = 0x1fffd;    // Following is an entire abs update.
     // Part of initial delta int holding the command code.
     static final int DELTA_CMD_MASK = 0x3;
-    static final int DELTA_CMD_SHIFT = 18;
+    static final int DELTA_CMD_SHIFT = 17;
     // Flag in delta int: a new battery level int follows.
-    static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20;
+    static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
     // Flag in delta int: a new full state and battery status int follows.
-    static final int DELTA_STATE_FLAG = 1<<21;
+    static final int DELTA_STATE_FLAG = 0x00100000;
+    // Flag in delta int: contains a wakelock tag.
+    static final int DELTA_WAKELOCK_FLAG = 0x00200000;
     static final int DELTA_STATE_MASK = 0xffc00000;
 
     public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
@@ -1532,6 +1560,9 @@
         if (stateIntChanged) {
             firstToken |= DELTA_STATE_FLAG;
         }
+        if (cur.wakelockTag != null) {
+            firstToken |= DELTA_WAKELOCK_FLAG;
+        }
         dest.writeInt(firstToken);
         if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
                 + " deltaTime=" + deltaTime);
@@ -1562,20 +1593,19 @@
                     + " batteryPlugType=" + cur.batteryPlugType
                     + " states=0x" + Integer.toHexString(cur.states));
         }
+        if (cur.wakelockTag != null) {
+            int index = writeHistoryTag(cur.wakelockTag);
+            dest.writeInt(index);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+                + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+        }
         if (cur.cmd == HistoryItem.CMD_EVENT) {
-            Integer idxObj = mHistoryStringPool.get(cur.eventName);
-            int codeAndIndex = (cur.eventCode&0xffff);
-            int idx;
-            if (idxObj != null) {
-                idx = idxObj;
-            } else {
-                idx = mNextHistoryStringIdx;
-                mHistoryStringPool.put(cur.eventName, mNextHistoryStringIdx);
-                mNextHistoryStringIdx++;
-            }
-            codeAndIndex |= (idx<<16);
+            int index = writeHistoryTag(cur.eventTag);
+            int codeAndIndex = (cur.eventCode&0xffff) | (index<<16);
             dest.writeInt(codeAndIndex);
-            dest.writeInt(cur.eventUid);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+                    + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+                    + cur.eventTag.string);
         }
     }
 
@@ -1596,6 +1626,7 @@
         int firstToken = src.readInt();
         int deltaTimeToken = firstToken&DELTA_TIME_MASK;
         cur.cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK);
+        cur.numReadInts = 1;
         if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
                 + " deltaTimeToken=" + deltaTimeToken);
 
@@ -1603,16 +1634,20 @@
             cur.time += deltaTimeToken;
         } else if (deltaTimeToken == DELTA_TIME_ABS) {
             cur.time = src.readLong();
+            cur.numReadInts += 2;
+            if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
             cur.readFromParcel(src);
             return;
         } else if (deltaTimeToken == DELTA_TIME_INT) {
             int delta = src.readInt();
             cur.time += delta;
+            cur.numReadInts += 1;
             if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
         } else {
             long delta = src.readLong();
             if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
             cur.time += delta;
+            cur.numReadInts += 2;
         }
 
         if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
@@ -1620,6 +1655,7 @@
             cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
             cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
             cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+            cur.numReadInts += 1;
             if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
                     + Integer.toHexString(batteryLevelInt)
                     + " batteryLevel=" + cur.batteryLevel
@@ -1633,6 +1669,7 @@
             cur.batteryStatus = (byte)((stateInt>>28)&0xf);
             cur.batteryHealth = (byte)((stateInt>>24)&0xf);
             cur.batteryPlugType = (byte)((stateInt>>22)&0x3);
+            cur.numReadInts += 1;
             if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
                     + Integer.toHexString(stateInt)
                     + " batteryStatus=" + cur.batteryStatus
@@ -1643,55 +1680,78 @@
             cur.states = (firstToken&DELTA_STATE_MASK) | (cur.states&(~DELTA_STATE_MASK));
         }
 
+        if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) {
+            cur.wakelockTag = cur.localWakelockTag;
+            int index = src.readInt();
+            readHistoryTag(index, cur.wakelockTag);
+            cur.numReadInts += 1;
+            if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+                + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+        } else {
+            cur.wakelockTag = null;
+        }
+
         if (cur.cmd == HistoryItem.CMD_EVENT) {
-            int codeAndIndex = src.readInt();
+            cur.eventTag = cur.localEventTag;
+            final int codeAndIndex = src.readInt();
             cur.eventCode = (codeAndIndex&0xffff);
-            int index = ((codeAndIndex>>16)&0xffff);
-            cur.eventName = mReadHistoryStrings[index];
-            cur.eventNameIdx = index;
-            cur.eventUid = src.readInt();
+            final int index = ((codeAndIndex>>16)&0xffff);
+            readHistoryTag(index, cur.eventTag);
+            cur.numReadInts += 1;
+            if (DEBUG) Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#"
+                    + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+                    + cur.eventTag.string);
         } else {
             cur.eventCode = HistoryItem.EVENT_NONE;
         }
     }
 
-    int mChangedBufferStates = 0;
-
     void addHistoryBufferLocked(long curTime) {
         if (!mHaveBatteryLevel || !mRecordingHistory) {
             return;
         }
 
         final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+        final int diffStates = mHistoryLastWritten.states^mHistoryCur.states;
+        final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
+        if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff="
+                + Integer.toHexString(diffStates) + " lastDiff="
+                + Integer.toHexString(lastDiffStates));
         if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
-                && timeDiff < 2000
-                && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
-            // If the current is the same as the one before, then we no
-            // longer need the entry.
+                && timeDiff < 1000 && (diffStates&lastDiffStates) == 0
+                && (mHistoryLastWritten.wakelockTag == null || mHistoryCur.wakelockTag == null)
+                && mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel
+                && mHistoryLastWritten.batteryStatus == mHistoryCur.batteryStatus
+                && mHistoryLastWritten.batteryHealth == mHistoryCur.batteryHealth
+                && mHistoryLastWritten.batteryPlugType == mHistoryCur.batteryPlugType
+                && mHistoryLastWritten.batteryTemperature == mHistoryCur.batteryTemperature
+                && mHistoryLastWritten.batteryVoltage == mHistoryCur.batteryVoltage) {
+            // We can merge this new change in with the last one.  Merging is
+            // allows 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;
-            if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
-                    && timeDiff < 500 && mHistoryLastLastWritten.sameNonEvent(mHistoryCur)) {
-                // If this results in us returning to the state written
-                // prior to the last one, then we can just delete the last
-                // written one and drop the new one.  Nothing more to do.
-                mHistoryLastWritten.setTo(mHistoryLastLastWritten);
-                mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
-                return;
-            }
-            mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
             curTime = mHistoryLastWritten.time - mHistoryBaseTime;
+            // 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) {
+                mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                mHistoryCur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+            }
             mHistoryLastWritten.setTo(mHistoryLastLastWritten);
-        } else {
-            mChangedBufferStates = 0;
         }
 
         final int dataSize = mHistoryBuffer.dataSize();
         if (dataSize >= MAX_HISTORY_BUFFER) {
             if (!mHistoryOverflow) {
                 mHistoryOverflow = true;
+                addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
                 addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+                return;
             }
 
             // Once we've reached the maximum number of items, we only
@@ -1704,6 +1764,9 @@
                                     & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
                 return;
             }
+
+            addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+            return;
         }
 
         addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
@@ -1719,10 +1782,8 @@
 
     private void addHistoryBufferLocked(long curTime, byte cmd,
             int eventCode, String eventName, int eventUid) {
-        int origPos = 0;
         if (mIteratingHistory) {
-            origPos = mHistoryBuffer.dataPosition();
-            mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+            throw new IllegalStateException("Can't do this while iterating history!");
         }
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
@@ -1730,12 +1791,10 @@
                 eventCode, eventUid, eventName, mHistoryCur);
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
         mLastHistoryTime = curTime;
+        mHistoryCur.wakelockTag = null;
         if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
                 + " now " + mHistoryBuffer.dataPosition()
                 + " size is now " + mHistoryBuffer.dataSize());
-        if (mIteratingHistory) {
-            mHistoryBuffer.setDataPosition(origPos);
-        }
     }
 
     int mChangedStates = 0;
@@ -1845,11 +1904,12 @@
 
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
-        mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
+        mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2);
         mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
         mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
-        mHistoryStringPool.clear();
-        mNextHistoryStringIdx = 0;
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
         mHistoryBufferLastPos = -1;
         mHistoryOverflow = false;
     }
@@ -1914,6 +1974,9 @@
                 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 = name;
+                mHistoryCur.wakelockTag.uid = uid;
                 addHistoryRecordLocked(SystemClock.elapsedRealtime());
             }
             mWakeLockNesting++;
@@ -2161,7 +2224,7 @@
 
             // Fake a wake lock, so we consider the device waked as long
             // as the screen is on.
-            noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
+            noteStartWakeLocked(-1, -1, "screen", WAKE_TYPE_PARTIAL);
             
             // Update discharge amounts.
             if (mOnBatteryInternal) {
@@ -4854,11 +4917,14 @@
     public boolean startIteratingOldHistoryLocked() {
         if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
                 + " pos=" + mHistoryBuffer.dataPosition());
+        if ((mHistoryIterator = mHistory) == null) {
+            return false;
+        }
         mHistoryBuffer.setDataPosition(0);
         mHistoryReadTmp.clear();
         mReadOverflow = false;
         mIteratingHistory = true;
-        return (mHistoryIterator = mHistory) != null;
+        return true;
     }
 
     @Override
@@ -4898,6 +4964,15 @@
     public void finishIteratingOldHistoryLocked() {
         mIteratingHistory = false;
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+        mHistoryIterator = null;
+    }
+
+    public int getHistoryTotalSize() {
+        return MAX_HISTORY_BUFFER;
+    }
+
+    public int getHistoryUsedSize() {
+        return mHistoryBuffer.dataSize();
     }
 
     @Override
@@ -4907,9 +4982,15 @@
         mHistoryBuffer.setDataPosition(0);
         mReadOverflow = false;
         mIteratingHistory = true;
-        mReadHistoryStrings = new String[mHistoryStringPool.size()];
-        for (HashMap.Entry<String, Integer> ent : mHistoryStringPool.entrySet()) {
-            mReadHistoryStrings[ent.getValue()] = ent.getKey();
+        mReadHistoryStrings = new String[mHistoryTagPool.size()];
+        mReadHistoryUids = new int[mHistoryTagPool.size()];
+        mReadHistoryChars = 0;
+        for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+            final HistoryTag tag = ent.getKey();
+            final int idx = ent.getValue();
+            mReadHistoryStrings[idx] = tag.string;
+            mReadHistoryUids[idx] = tag.uid;
+            mReadHistoryChars += tag.string.length() + 1;
         }
         return mHistoryBuffer.dataSize() > 0;
     }
@@ -4920,11 +5001,23 @@
     }
 
     @Override
-    public String getHistoryStringPoolItem(int index) {
+    public int getHistoryStringPoolBytes() {
+        // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size
+        // Each string character is 2 bytes.
+        return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2);
+    }
+
+    @Override
+    public String getHistoryTagPoolString(int index) {
         return mReadHistoryStrings[index];
     }
 
     @Override
+    public int getHistoryTagPoolUid(int index) {
+        return mReadHistoryUids[index];
+    }
+
+    @Override
     public boolean getNextHistoryLocked(HistoryItem out) {
         final int pos = mHistoryBuffer.dataPosition();
         if (pos == 0) {
@@ -5717,17 +5810,24 @@
 
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
-        mHistoryStringPool.clear();
-        mNextHistoryStringIdx = 0;
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
 
-        int numStrings = in.readInt();
-        for (int i=0; i<numStrings; i++) {
-            String str = in.readString();
+        int numTags = in.readInt();
+        for (int i=0; i<numTags; i++) {
             int idx = in.readInt();
-            mHistoryStringPool.put(str, idx);
-            if (idx >= mNextHistoryStringIdx) {
-                mNextHistoryStringIdx = idx+1;
+            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;
         }
 
         int bufSize = in.readInt();
@@ -5797,10 +5897,12 @@
             Slog.i(TAG, sb.toString());
         }
         out.writeLong(mHistoryBaseTime + mLastHistoryTime);
-        out.writeInt(mHistoryStringPool.size());
-        for (HashMap.Entry<String, Integer> ent : mHistoryStringPool.entrySet()) {
-            out.writeString(ent.getKey());
+        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);
         }
         out.writeInt(mHistoryBuffer.dataSize());
         if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "