Merge "Have BatteryStats track and report the running wakelocks." into nyc-mr1-dev
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index f0cc390..a0c2efd 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -30,6 +30,8 @@
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.Pair;
 import android.util.Printer;
@@ -47,6 +49,7 @@
  * @hide
  */
 public abstract class BatteryStats implements Parcelable {
+    private static final String TAG = "BatteryStats";
 
     private static final boolean LOCAL_LOGV = false;
 
@@ -175,8 +178,11 @@
 
     /**
      * Current version of checkin data format.
+     *
+     * New in version 19:
+     *   - Wakelock data (wl) gets current and max times.
      */
-    static final String CHECKIN_VERSION = "18";
+    static final String CHECKIN_VERSION = "19";
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -352,6 +358,32 @@
         public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs);
 
         /**
+         * Returns the max duration if it is being tracked.
+         * Not all Timer subclasses track the max duration and the current duration.
+
+         */
+        public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
+         * Returns the current time the timer has been active, if it is being tracked.
+         * Not all Timer subclasses track the max duration and the current duration.
+         */
+        public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
+         * Returns whether the timer is currently running.  Some types of timers
+         * (e.g. BatchTimers) don't know whether the event is currently active,
+         * and report false.
+         */
+        public boolean isRunningLocked() {
+            return false;
+        }
+
+        /**
          * Temporary for debugging.
          */
         public abstract void logState(Printer pw, String prefix);
@@ -2558,6 +2590,22 @@
                 sb.append('(');
                 sb.append(count);
                 sb.append(" times)");
+                final long maxDurationMs = timer.getMaxDurationMsLocked(elapsedRealtimeUs/1000);
+                if (maxDurationMs >= 0) {
+                    sb.append(" max=");
+                    sb.append(maxDurationMs);
+                }
+                if (timer.isRunningLocked()) {
+                    final long currentMs = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
+                    if (currentMs >= 0) {
+                        sb.append(" (running for ");
+                        sb.append(currentMs);
+                        sb.append("ms)");
+                    } else {
+                        sb.append(" (running)");
+                    }
+                }
+
                 return ", ";
             }
         }
@@ -2565,6 +2613,7 @@
     }
 
     /**
+     * Prints details about a timer, if its total time was greater than 0.
      *
      * @param pw a PrintWriter object to print to.
      * @param sb a StringBuilder object.
@@ -2573,24 +2622,40 @@
      * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
      * @param prefix a String to be prepended to each line of output.
      * @param type the name of the timer.
+     * @return true if anything was printed.
      */
     private static final boolean printTimer(PrintWriter pw, StringBuilder sb, Timer timer,
-            long rawRealtime, int which, String prefix, String type) {
+            long rawRealtimeUs, int which, String prefix, String type) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
-            final long totalTime = (timer.getTotalTimeLocked(
-                    rawRealtime, which) + 500) / 1000;
+            final long totalTimeMs = (timer.getTotalTimeLocked(
+                    rawRealtimeUs, which) + 500) / 1000;
             final int count = timer.getCountLocked(which);
-            if (totalTime != 0) {
+            if (totalTimeMs != 0) {
                 sb.setLength(0);
                 sb.append(prefix);
                 sb.append("    ");
                 sb.append(type);
                 sb.append(": ");
-                formatTimeMs(sb, totalTime);
+                formatTimeMs(sb, totalTimeMs);
                 sb.append("realtime (");
                 sb.append(count);
                 sb.append(" times)");
+                final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs/1000);
+                if (maxDurationMs >= 0) {
+                    sb.append(" max=");
+                    sb.append(maxDurationMs);
+                }
+                if (timer.isRunningLocked()) {
+                    final long currentMs = timer.getCurrentDurationMsLocked(rawRealtimeUs/1000);
+                    if (currentMs >= 0) {
+                        sb.append(" (running for ");
+                        sb.append(currentMs);
+                        sb.append("ms)");
+                    } else {
+                        sb.append(" (running)");
+                    }
+                }
                 pw.println(sb.toString());
                 return true;
             }
@@ -2613,15 +2678,23 @@
             long elapsedRealtimeUs, String name, int which, String linePrefix) {
         long totalTimeMicros = 0;
         int count = 0;
+        long max = -1;
+        long current = -1;
         if (timer != null) {
             totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
             count = timer.getCountLocked(which); 
+            current = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
+            max = timer.getMaxDurationMsLocked(elapsedRealtimeUs/1000);
         }
         sb.append(linePrefix);
         sb.append((totalTimeMicros + 500) / 1000); // microseconds to milliseconds with rounding
         sb.append(',');
         sb.append(name != null ? name + "," : "");
         sb.append(count);
+        sb.append(',');
+        sb.append(current);
+        sb.append(',');
+        sb.append(max);
         return ",";
     }
     
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 17635ae..7fb92ae 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -108,7 +108,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 148 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 149 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -1566,6 +1566,186 @@
         }
     }
 
+
+    /**
+     * A StopwatchTimer that also tracks the total and max individual
+     * time spent active according to the given timebase.  Whereas
+     * StopwatchTimer apportions the time amongst all in the pool,
+     * the total and max durations are not apportioned.
+     */
+    public static class DurationTimer extends StopwatchTimer {
+        /**
+         * The time (in ms) that the timer was last acquired or the time base
+         * last (re-)started. Increasing the nesting depth does not reset this time.
+         *
+         * -1 if the timer is currently not running or the time base is not running.
+         *
+         * If written to a parcel, the start time is reset, as is mNesting in the base class
+         * StopwatchTimer.
+         */
+        long mStartTimeMs = -1;
+
+        /**
+         * The longest time period (in ms) that the timer has been active.
+         */
+        long mMaxDurationMs;
+
+        /**
+         * The total time (in ms) that that the timer has been active since reset().
+         */
+        long mCurrentDurationMs;
+
+        public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, Parcel in) {
+            super(clocks, uid, type, timerPool, timeBase, in);
+            mMaxDurationMs = in.readLong();
+        }
+
+        public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase) {
+            super(clocks, uid, type, timerPool, timeBase);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            out.writeLong(mMaxDurationMs);
+        }
+
+        /**
+         * Write the summary to the parcel.
+         *
+         * Since the time base is probably meaningless after we come back, reading
+         * from this will have the effect of stopping the timer. So here all we write
+         * is the max duration.
+         */
+        @Override
+        public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+            super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+            out.writeLong(mMaxDurationMs);
+        }
+
+        /**
+         * Read the summary parcel.
+         *
+         * Has the side effect of stopping the timer.
+         */
+        @Override
+        public void readSummaryFromParcelLocked(Parcel in) {
+            super.readSummaryFromParcelLocked(in);
+            mMaxDurationMs = in.readLong();
+            mStartTimeMs = -1;
+            mCurrentDurationMs = 0;
+        }
+
+        /**
+         * The TimeBase time started (again).
+         *
+         * If the timer is also running, store the start time.
+         */
+        public void onTimeStarted(long elapsedRealtimeUs, long baseUptime, long baseRealtime) {
+            super.onTimeStarted(elapsedRealtimeUs, baseUptime, baseRealtime);
+            if (mNesting > 0) {
+                mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
+            }
+        }
+
+        /**
+         * The TimeBase stopped running.
+         *
+         * If the timer is running, add the duration into mCurrentDurationMs.
+         */
+        @Override
+        public void onTimeStopped(long elapsedRealtimeUs, long baseUptime, long baseRealtime) {
+            super.onTimeStopped(elapsedRealtimeUs, baseUptime, baseRealtime);
+            if (mNesting > 0) {
+                mCurrentDurationMs += (elapsedRealtimeUs / 1000) - mStartTimeMs;
+            }
+            mStartTimeMs = -1;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+        }
+
+        @Override
+        public void startRunningLocked(long elapsedRealtimeMs) {
+            super.startRunningLocked(elapsedRealtimeMs);
+            if (mNesting == 1 && mTimeBase.isRunning()) {
+                // Just started
+                mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
+            }
+        }
+
+        /**
+         * Decrements the mNesting ref-count on this timer.
+         *
+         * If it actually stopped (mNesting went to 0), then possibly update
+         * mMaxDuration if the current duration was the longest ever.
+         */
+        @Override
+        public void stopRunningLocked(long elapsedRealtimeMs) {
+            super.stopRunningLocked(elapsedRealtimeMs);
+            if (mNesting == 0) {
+                final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+                if (durationMs > mMaxDurationMs) {
+                    mMaxDurationMs = durationMs;
+                }
+                mStartTimeMs = -1;
+                mCurrentDurationMs = 0;
+            }
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            boolean result = super.reset(detachIfReset);
+            mMaxDurationMs = 0;
+            mCurrentDurationMs = 0;
+            if (mNesting > 0) {
+                mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
+            } else {
+                mStartTimeMs = -1;
+            }
+            return result;
+        }
+
+        /**
+         * Returns the max duration that this timer has ever seen.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time.
+         */
+        @Override
+        public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
+            if (mNesting > 0) {
+                final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+                if (durationMs > mMaxDurationMs) {
+                    return durationMs;
+                }
+            }
+            return mMaxDurationMs;
+        }
+
+        /**
+         * Returns the time since the timer was started.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time.
+         */
+        @Override
+        public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
+            long durationMs = mCurrentDurationMs;
+            if (mNesting > 0) {
+                if (mTimeBase.isRunning()) {
+                    durationMs += (mTimeBase.getRealtime(elapsedRealtimeMs*1000)/1000)
+                            - mStartTimeMs;
+                }
+            }
+            return durationMs;
+        }
+    }
+
     /**
      * State for keeping track of timing information.
      */
@@ -6535,7 +6715,7 @@
             /**
              * How long (in ms) this uid has been keeping the device partially awake.
              */
-            StopwatchTimer mTimerPartial;
+            DurationTimer mTimerPartial;
 
             /**
              * How long (in ms) this uid has been keeping the device fully awake.
@@ -6564,8 +6744,8 @@
              * @param in the Parcel to be read from.
              * return a new Timer, or null.
              */
-            private StopwatchTimer readTimerFromParcel(int type, ArrayList<StopwatchTimer> pool,
-                    TimeBase timeBase, Parcel in) {
+            private StopwatchTimer readStopwatchTimerFromParcel(int type,
+                    ArrayList<StopwatchTimer> pool, TimeBase timeBase, Parcel in) {
                 if (in.readInt() == 0) {
                     return null;
                 }
@@ -6573,6 +6753,22 @@
                 return new StopwatchTimer(mBsi.mClocks, mUid, type, pool, timeBase, in);
             }
 
+            /**
+             * Reads a possibly null Timer from a Parcel.  The timer is associated with the
+             * proper timer pool from the given BatteryStatsImpl object.
+             *
+             * @param in the Parcel to be read from.
+             * return a new Timer, or null.
+             */
+            private DurationTimer readDurationTimerFromParcel(int type,
+                    ArrayList<StopwatchTimer> pool, TimeBase timeBase, Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                return new DurationTimer(mBsi.mClocks, mUid, type, pool, timeBase, in);
+            }
+
             boolean reset() {
                 boolean wlactive = false;
                 if (mTimerFull != null) {
@@ -6609,11 +6805,14 @@
             }
 
             void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
-                mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
+                mTimerPartial = readDurationTimerFromParcel(WAKE_TYPE_PARTIAL,
                         mBsi.mPartialTimers, screenOffTimeBase, in);
-                mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL, mBsi.mFullTimers, timeBase, in);
-                mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW, mBsi.mWindowTimers, timeBase, in);
-                mTimerDraw = readTimerFromParcel(WAKE_TYPE_DRAW, mBsi.mDrawTimers, timeBase, in);
+                mTimerFull = readStopwatchTimerFromParcel(WAKE_TYPE_FULL,
+                        mBsi.mFullTimers, timeBase, in);
+                mTimerWindow = readStopwatchTimerFromParcel(WAKE_TYPE_WINDOW,
+                        mBsi.mWindowTimers, timeBase, in);
+                mTimerDraw = readStopwatchTimerFromParcel(WAKE_TYPE_DRAW,
+                        mBsi.mDrawTimers, timeBase, in);
             }
 
             void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
@@ -6635,40 +6834,43 @@
             }
 
             public StopwatchTimer getStopwatchTimer(int type) {
-                StopwatchTimer t;
                 switch (type) {
-                    case WAKE_TYPE_PARTIAL:
-                        t = mTimerPartial;
+                    case WAKE_TYPE_PARTIAL: {
+                        DurationTimer t = mTimerPartial;
                         if (t == null) {
-                            t = new StopwatchTimer(mBsi.mClocks, mUid, WAKE_TYPE_PARTIAL,
+                            t = new DurationTimer(mBsi.mClocks, mUid, WAKE_TYPE_PARTIAL,
                                     mBsi.mPartialTimers, mBsi.mOnBatteryScreenOffTimeBase);
                             mTimerPartial = t;
                         }
                         return t;
-                    case WAKE_TYPE_FULL:
-                        t = mTimerFull;
+                    }
+                    case WAKE_TYPE_FULL: {
+                        StopwatchTimer t = mTimerFull;
                         if (t == null) {
                             t = new StopwatchTimer(mBsi.mClocks, mUid, WAKE_TYPE_FULL,
                                     mBsi.mFullTimers, mBsi.mOnBatteryTimeBase);
                             mTimerFull = t;
                         }
                         return t;
-                    case WAKE_TYPE_WINDOW:
-                        t = mTimerWindow;
+                    }
+                    case WAKE_TYPE_WINDOW: {
+                        StopwatchTimer t = mTimerWindow;
                         if (t == null) {
                             t = new StopwatchTimer(mBsi.mClocks, mUid, WAKE_TYPE_WINDOW,
                                     mBsi.mWindowTimers, mBsi.mOnBatteryTimeBase);
                             mTimerWindow = t;
                         }
                         return t;
-                    case WAKE_TYPE_DRAW:
-                        t = mTimerDraw;
+                    }
+                    case WAKE_TYPE_DRAW: {
+                        StopwatchTimer t = mTimerDraw;
                         if (t == null) {
                             t = new StopwatchTimer(mBsi.mClocks, mUid, WAKE_TYPE_DRAW,
                                     mBsi.mDrawTimers, mBsi.mOnBatteryTimeBase);
                             mTimerDraw = t;
                         }
                         return t;
+                    }
                     default:
                         throw new IllegalArgumentException("type=" + type);
                 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
new file mode 100644
index 0000000..a15e367
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mockito;
+
+/**
+ * Test BatteryStatsImpl.DurationTimer.
+ *
+ * In these tests, unless otherwise commented, the time increments by
+ * 2x + 100, to make the subtraction unlikely to alias to another time.
+ */
+public class BatteryStatsDurationTimerTest extends TestCase {
+
+    @SmallTest
+    public void testStartStop() throws Exception {
+        final MockClocks clocks = new MockClocks();
+
+        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
+        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());
+
+        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, 
+                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);
+
+        // TimeBase running, timer not running: current and max are 0
+        timeBase.setRunning(true, /* uptimeUs */ 0, /* realtimeUs */ 100*1000);
+        assertFalse(timer.isRunningLocked());
+        assertEquals(0, timer.getCurrentDurationMsLocked(300));
+        assertEquals(0, timer.getMaxDurationMsLocked(301));
+
+        // Start timer: current and max advance
+        timer.startRunningLocked(700);
+        assertTrue(timer.isRunningLocked());
+        assertEquals(800, timer.getCurrentDurationMsLocked(1500));
+        assertEquals(801, timer.getMaxDurationMsLocked(1501));
+
+        // Stop timer: current resets to 0, max remains
+        timer.stopRunningLocked(3100);
+        assertFalse(timer.isRunningLocked());
+        assertEquals(0, timer.getCurrentDurationMsLocked(6300));
+        assertEquals(2400, timer.getMaxDurationMsLocked(6301));
+
+        // Start time again, but check with a short time, and make sure max doesn't
+        // increment.
+        timer.startRunningLocked(12700);
+        assertTrue(timer.isRunningLocked());
+        assertEquals(100, timer.getCurrentDurationMsLocked(12800));
+        assertEquals(2400, timer.getMaxDurationMsLocked(12801));
+
+        // And stop it again, but with a short time, and make sure it doesn't increment.
+        timer.stopRunningLocked(12900);
+        assertFalse(timer.isRunningLocked());
+        assertEquals(0, timer.getCurrentDurationMsLocked(13000));
+        assertEquals(2400, timer.getMaxDurationMsLocked(13001));
+
+        // Now start and check that the time doesn't increase if the two times are the same.
+        timer.startRunningLocked(27000);
+        assertTrue(timer.isRunningLocked());
+        assertEquals(0, timer.getCurrentDurationMsLocked(27000));
+        assertEquals(2400, timer.getMaxDurationMsLocked(27000));
+
+        // Stop the TimeBase. The values should be frozen.
+        timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000);
+        assertTrue(timer.isRunningLocked());
+        assertEquals(28100, timer.getCurrentDurationMsLocked(110100)); // Why 28100 and not 28000?
+        assertEquals(28100, timer.getMaxDurationMsLocked(110101));
+
+        // Start the TimeBase. The values should be the old value plus the delta
+        // between when the timer restarted and the current time
+        timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000);
+        assertTrue(timer.isRunningLocked());
+        assertEquals(28300, timer.getCurrentDurationMsLocked(220300)); // extra 100 from above??
+        assertEquals(28301, timer.getMaxDurationMsLocked(220301));
+    }
+
+    @SmallTest
+    public void testReset() throws Exception {
+    }
+
+    @SmallTest
+    public void testParceling() throws Exception {
+        final MockClocks clocks = new MockClocks();
+
+        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
+        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());
+
+        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, 
+                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);
+
+        // Start running on battery.
+        clocks.realtime = 100;
+        clocks.uptime = 10;
+        timeBase.setRunning(true, clocks.uptimeMillis()*1000, clocks.elapsedRealtime()*1000);
+
+        timer.startRunningLocked(300);
+
+        // Check that it did start running
+        assertEquals(400, timer.getMaxDurationMsLocked(700));
+        assertEquals(401, timer.getCurrentDurationMsLocked(701));
+
+        // Write summary
+        final Parcel summaryParcel = Parcel.obtain();
+        timer.writeSummaryFromParcelLocked(summaryParcel, 1500*1000);
+        summaryParcel.setDataPosition(0);
+
+        // Read summary
+        final BatteryStatsImpl.DurationTimer summary = new BatteryStatsImpl.DurationTimer(clocks, 
+                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);
+        summary.startRunningLocked(3100);
+        summary.readSummaryFromParcelLocked(summaryParcel);
+        // The new one shouldn't be running, and therefore 0 for current time
+        assertFalse(summary.isRunningLocked());
+        assertEquals(0, summary.getCurrentDurationMsLocked(6300));
+        // The new one should have the max duration that we had when we wrote it
+        assertEquals(1200, summary.getMaxDurationMsLocked(6301));
+
+        // Write full
+        final Parcel fullParcel = Parcel.obtain();
+        timer.writeToParcel(fullParcel, 1500*1000);
+        fullParcel.setDataPosition(0);
+ 
+        // Read full - Should be the same as the summary as far as DurationTimer is concerned.
+        final BatteryStatsImpl.DurationTimer full = new BatteryStatsImpl.DurationTimer(clocks, 
+                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase, fullParcel);
+        // The new one shouldn't be running, and therefore 0 for current time
+        assertFalse(full.isRunningLocked());
+        assertEquals(0, full.getCurrentDurationMsLocked(6300));
+        // The new one should have the max duration that we had when we wrote it
+        assertEquals(1200, full.getMaxDurationMsLocked(6301));
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 78bcbbc..9518219 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -5,6 +5,7 @@
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+        BatteryStatsDurationTimerTest.class,
         BatteryStatsSamplingTimerTest.class,
         BatteryStatsServTest.class,
         BatteryStatsTimeBaseTest.class,