Various battery info things:

- Now track wake locks in battery history.
- Now track sensors in battery history.
- Some filtering of sensory data.
- Fixes to some data that wasn't cleared when resetting battery stats.
- Print amount discharged since last charge.

And the big part -- keep track of wake locks held per process,
and kill processes that hold wake locks too much while they are in
the background.  This includes information in the battery stats
about the process being killed, which will be available to the
developer if the app is reported.

Change-Id: I97202e94d00aafe0526ba2db74a03212e7539c54
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d66e98b..d5741fc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -365,7 +365,8 @@
         
         /**
          * The time when the service was first made active, either by someone
-         * starting or binding to it.
+         * starting or binding to it.  This
+         * is in units of {@link android.os.SystemClock#elapsedRealtime()}.
          */
         public long activeSince;
         
@@ -387,7 +388,8 @@
         
         /**
          * The time when there was last activity in the service (either
-         * explicit requests to start it or clients binding to it).
+         * explicit requests to start it or clients binding to it).  This
+         * is in units of {@link android.os.SystemClock#uptimeMillis()}.
          */
         public long lastActivityTime;
         
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a699388..a0a3bdf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -290,6 +290,11 @@
          */
         public static abstract class Proc {
 
+            public static class ExcessiveWake {
+                public long overTime;
+                public long usedTime;
+            }
+
             /**
              * Returns the total time (in 1/100 sec) spent executing in user code.
              *
@@ -326,6 +331,10 @@
              * @see BatteryStats#getCpuSpeedSteps()
              */
             public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
+
+            public abstract int countExcessiveWakes();
+
+            public abstract ExcessiveWake getExcessiveWake(int i);
         }
 
         /**
@@ -421,6 +430,8 @@
         public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20;
         public static final int STATE_AUDIO_ON_FLAG = 1<<19;
         public static final int STATE_VIDEO_ON_FLAG = 1<<18;
+        public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
+        public static final int STATE_SENSOR_ON_FLAG = 1<<16;
         
         public int states;
 
@@ -470,6 +481,16 @@
             batteryVoltage = o.batteryVoltage;
             states = o.states;
         }
+
+        public boolean same(HistoryItem o) {
+            return batteryLevel == o.batteryLevel
+                    && batteryStatus == o.batteryStatus
+                    && batteryHealth == o.batteryHealth
+                    && batteryPlugType == o.batteryPlugType
+                    && batteryTemperature == o.batteryTemperature
+                    && batteryVoltage == o.batteryVoltage
+                    && states == o.states;
+        }
     }
     
     public static final class BitDescription {
@@ -633,6 +654,8 @@
         new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"),
         new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"),
         new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"),
+        new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"),
+        new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"),
         new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
                 HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness",
                 SCREEN_BRIGHTNESS_NAMES),
@@ -1376,7 +1399,6 @@
                         pw.println(getDischargeStartLevel());
                 pw.print(prefix); pw.print("    Discharge cycle current level: ");
                         pw.println(getDischargeCurrentLevel());
-            } else {
                 pw.print(prefix); pw.println("  Device is currently plugged into power");
                 pw.print(prefix); pw.print("    Last discharge cycle start level: "); 
                         pw.println(getDischargeStartLevel());
@@ -1384,6 +1406,13 @@
                         pw.println(getDischargeCurrentLevel());
             }
             pw.println(" ");
+        } else {
+            pw.print(prefix); pw.println("  Device battery use since last full charge");
+            pw.print(prefix); pw.print("    Amount discharged (lower bound): ");
+                    pw.println(getLowDischargeAmountSinceCharge());
+            pw.print(prefix); pw.print("    Amount discharged (upper bound): ");
+                    pw.println(getHighDischargeAmountSinceCharge());
+            pw.println(" ");
         }
         
 
@@ -1524,12 +1553,16 @@
                     long userTime;
                     long systemTime;
                     int starts;
+                    int numExcessive;
 
                     userTime = ps.getUserTime(which);
                     systemTime = ps.getSystemTime(which);
                     starts = ps.getStarts(which);
+                    numExcessive = which == STATS_SINCE_CHARGED
+                            ? ps.countExcessiveWakes() : 0;
 
-                    if (userTime != 0 || systemTime != 0 || starts != 0) {
+                    if (userTime != 0 || systemTime != 0 || starts != 0
+                            || numExcessive != 0) {
                         sb.setLength(0);
                         sb.append(prefix); sb.append("    Proc ");
                                 sb.append(ent.getKey()); sb.append(":\n");
@@ -1539,6 +1572,16 @@
                         sb.append(prefix); sb.append("      "); sb.append(starts);
                                 sb.append(" proc starts");
                         pw.println(sb.toString());
+                        for (int e=0; e<numExcessive; e++) {
+                            Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e);
+                            if (ew != null) {
+                                pw.print(prefix); pw.print("      * Killed for wake lock use: ");
+                                        pw.print(ew.usedTime); pw.print("ms over ");
+                                        pw.print(ew.overTime); pw.print("ms (");
+                                        pw.print((ew.usedTime*100)/ew.overTime);
+                                        pw.println("%)");
+                            }
+                        }
                         uidActivity = true;
                     }
                 }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index d040d3f..1620778 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -22,8 +22,8 @@
 
 interface IBatteryStats {
     byte[] getStatistics();
-    void noteStartWakelock(int uid, String name, int type);
-    void noteStopWakelock(int uid, String name, int type);
+    void noteStartWakelock(int uid, int pid, String name, int type);
+    void noteStopWakelock(int uid, int pid, String name, int type);
     
     /* DO NOT CHANGE the position of noteStartSensor without updating
        SensorService.cpp */
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b3323e9..2f26135 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -27,6 +27,7 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.BatteryStats.Uid.Proc.ExcessiveWake;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -63,7 +64,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
 
     // Current on-disk Parcel version
-    private static final int VERSION = 49;
+    private static final int VERSION = 50;
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 1000;
@@ -107,6 +108,7 @@
     int mNumHistoryItems;
     HistoryItem mHistory;
     HistoryItem mHistoryEnd;
+    HistoryItem mHistoryLastEnd;
     HistoryItem mHistoryCache;
     final HistoryItem mHistoryCur = new HistoryItem();
     
@@ -451,7 +453,7 @@
          * Clear state of this timer.  Returns true if the timer is inactive
          * so can be completely dropped.
          */
-        boolean reset(boolean detachIfReset) {
+        boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
             mTotalTime = mLoadedTime = mLastTime = 0;
             mCount = mLoadedCount = mLastCount = 0;
             if (detachIfReset) {
@@ -713,8 +715,8 @@
             out.writeInt(mTrackingReportedValues ? 1 : 0);
         }
         
-        boolean reset(boolean detachIfReset) {
-            super.reset(detachIfReset);
+        boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+            super.reset(stats, detachIfReset);
             setStale();
             return true;
         }
@@ -749,7 +751,7 @@
         long mUpdateTime;
         
         /**
-         * The total time at which the timer was acquired, to determine if
+         * The total time at which the timer was acquired, to determine if it
          * was actually held for an interesting duration.
          */
         long mAcquireTime;
@@ -890,9 +892,14 @@
             return mCount;
         }
 
-        boolean reset(boolean detachIfReset) {
+        boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
             boolean canDetach = mNesting <= 0;
-            super.reset(canDetach && detachIfReset);
+            super.reset(stats, canDetach && detachIfReset);
+            if (mNesting > 0) {
+                mUpdateTime = stats.getBatteryRealtimeLocked(
+                        SystemClock.elapsedRealtime() * 1000);
+            }
+            mAcquireTime = mTotalTime;
             return canDetach;
         }
         
@@ -1113,6 +1120,26 @@
         if (!mHaveBatteryLevel || !mRecordingHistory) {
             return;
         }
+
+        // If the current time is basically the same as the last time,
+        // just collapse into one record.
+        if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
+                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+100)) {
+            // If the current is the same as the one before, then we no
+            // longer need the entry.
+            if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+                    && mHistoryLastEnd.same(mHistoryCur)) {
+                mHistoryLastEnd.next = null;
+                mHistoryEnd.next = mHistoryCache;
+                mHistoryCache = mHistoryEnd;
+                mHistoryEnd = mHistoryLastEnd;
+                mHistoryLastEnd = null;
+            } else {
+                mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur);
+            }
+            return;
+        }
+
         if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
             // Once we've reached the maximum number of items, we only
             // record changes to the battery level.
@@ -1121,6 +1148,7 @@
                 return;
             }
         }
+
         addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE);
     }
     
@@ -1139,6 +1167,7 @@
     void addHistoryRecordLocked(HistoryItem rec) {
         mNumHistoryItems++;
         rec.next = null;
+        mHistoryLastEnd = mHistoryEnd;
         if (mHistoryEnd != null) {
             mHistoryEnd.next = rec;
             mHistoryEnd = rec;
@@ -1151,7 +1180,7 @@
         if (mHistory != null) {
             mHistoryEnd.next = mHistoryCache;
             mHistoryCache = mHistory;
-            mHistory = mHistoryEnd = null;
+            mHistory = mHistoryLastEnd = mHistoryEnd = null;
         }
         mNumHistoryItems = 0;
         mHistoryBaseTime = 0;
@@ -1209,6 +1238,83 @@
         mBluetoothPingStart = -1;
     }
 
+    int mWakeLockNesting;
+
+    public void noteStartWakeLocked(int uid, int pid, String name, int type) {
+        if (mWakeLockNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(SystemClock.elapsedRealtime());
+        }
+        mWakeLockNesting++;
+        if (uid >= 0) {
+            getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
+        }
+    }
+
+    public void noteStopWakeLocked(int uid, int pid, String name, int type) {
+        mWakeLockNesting--;
+        if (mWakeLockNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(SystemClock.elapsedRealtime());
+        }
+        if (uid >= 0) {
+            getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
+        }
+    }
+
+    public void noteProcessDiedLocked(int uid, int pid) {
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            u.mPids.remove(pid);
+        }
+    }
+
+    public long getProcessWakeTime(int uid, int pid, long realtime) {
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            Uid.Pid p = u.mPids.get(pid);
+            if (p != null) {
+                return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0);
+            }
+        }
+        return 0;
+    }
+
+    public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) {
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            u.reportExcessiveWakeLocked(proc, overTime, usedTime);
+        }
+    }
+
+    int mSensorNesting;
+
+    public void noteStartSensorLocked(int uid, int sensor) {
+        if (mSensorNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(SystemClock.elapsedRealtime());
+        }
+        mSensorNesting++;
+        getUidStatsLocked(uid).noteStartSensor(sensor);
+    }
+
+    public void noteStopSensorLocked(int uid, int sensor) {
+        mSensorNesting--;
+        if (mSensorNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(SystemClock.elapsedRealtime());
+        }
+        getUidStatsLocked(uid).noteStopSensor(sensor);
+    }
+
     int mGpsNesting;
     
     public void noteStartGpsLocked(int uid) {
@@ -1244,6 +1350,10 @@
             if (mScreenBrightnessBin >= 0) {
                 mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
             }
+
+            // Fake a wake lock, so we consider the device waked as long
+            // as the screen is on.
+            noteStartWakeLocked(-1, -1, "dummy", 0);
         }
     }
     
@@ -1258,6 +1368,8 @@
             if (mScreenBrightnessBin >= 0) {
                 mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
             }
+
+            noteStopWakeLocked(-1, -1, "dummy", 0);
         }
     }
     
@@ -1798,6 +1910,11 @@
          */
         final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
         
+        /**
+         * The transient wake stats we have collected for this uid's pids.
+         */
+        final SparseArray<Pid> mPids = new SparseArray<Pid>();
+
         public Uid(int uid) {
             mUid = uid;
             mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables);
@@ -2081,27 +2198,27 @@
             boolean active = false;
             
             if (mWifiTurnedOnTimer != null) {
-                active |= !mWifiTurnedOnTimer.reset(false);
+                active |= !mWifiTurnedOnTimer.reset(BatteryStatsImpl.this, false);
                 active |= mWifiTurnedOn;
             }
             if (mFullWifiLockTimer != null) {
-                active |= !mFullWifiLockTimer.reset(false);
+                active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false);
                 active |= mFullWifiLockOut;
             }
             if (mScanWifiLockTimer != null) {
-                active |= !mScanWifiLockTimer.reset(false);
+                active |= !mScanWifiLockTimer.reset(BatteryStatsImpl.this, false);
                 active |= mScanWifiLockOut;
             }
             if (mWifiMulticastTimer != null) {
-                active |= !mWifiMulticastTimer.reset(false);
+                active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
                 active |= mWifiMulticastEnabled;
             }
             if (mAudioTurnedOnTimer != null) {
-                active |= !mAudioTurnedOnTimer.reset(false);
+                active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false);
                 active |= mAudioTurnedOn;
             }
             if (mVideoTurnedOnTimer != null) {
-                active |= !mVideoTurnedOnTimer.reset(false);
+                active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
                 active |= mVideoTurnedOn;
             }
             
@@ -2146,6 +2263,14 @@
                 }
                 mProcessStats.clear();
             }
+            if (mPids.size() > 0) {
+                for (int i=0; !active && i<mPids.size(); i++) {
+                    Pid pid = mPids.valueAt(i);
+                    if (pid.mWakeStart != 0) {
+                        active = true;
+                    }
+                }
+            }
             if (mPackageStats.size() > 0) {
                 Iterator<Map.Entry<String, Pkg>> it = mPackageStats.entrySet().iterator();
                 while (it.hasNext()) {
@@ -2164,6 +2289,8 @@
                 mPackageStats.clear();
             }
             
+            mPids.clear();
+
             if (!active) {
                 if (mWifiTurnedOnTimer != null) {
                     mWifiTurnedOnTimer.detach();
@@ -2412,13 +2539,13 @@
             boolean reset() {
                 boolean wlactive = false;
                 if (mTimerFull != null) {
-                    wlactive |= !mTimerFull.reset(false);
+                    wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false);
                 }
                 if (mTimerPartial != null) {
-                    wlactive |= !mTimerPartial.reset(false);
+                    wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false);
                 }
                 if (mTimerWindow != null) {
-                    wlactive |= !mTimerWindow.reset(false);
+                    wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false);
                 }
                 if (!wlactive) {
                     if (mTimerFull != null) {
@@ -2486,7 +2613,7 @@
             }
 
             boolean reset() {
-                if (mTimer.reset(true)) {
+                if (mTimer.reset(BatteryStatsImpl.this, true)) {
                     mTimer = null;
                     return true;
                 }
@@ -2598,6 +2725,8 @@
 
             SamplingCounter[] mSpeedBins;
 
+            ArrayList<ExcessiveWake> mExcessiveWake;
+
             Proc() {
                 mUnpluggables.add(this);
                 mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
@@ -2624,6 +2753,58 @@
                 }
             }
             
+            public int countExcessiveWakes() {
+                return mExcessiveWake != null ? mExcessiveWake.size() : 0;
+            }
+
+            public ExcessiveWake getExcessiveWake(int i) {
+                if (mExcessiveWake != null) {
+                    return mExcessiveWake.get(i);
+                }
+                return null;
+            }
+
+            public void addExcessiveWake(long overTime, long usedTime) {
+                if (mExcessiveWake == null) {
+                    mExcessiveWake = new ArrayList<ExcessiveWake>();
+                }
+                ExcessiveWake ew = new ExcessiveWake();
+                ew.overTime = overTime;
+                ew.usedTime = usedTime;
+                mExcessiveWake.add(ew);
+            }
+
+            void writeExcessiveWakeToParcelLocked(Parcel out) {
+                if (mExcessiveWake == null) {
+                    out.writeInt(0);
+                    return;
+                }
+
+                final int N = mExcessiveWake.size();
+                out.writeInt(N);
+                for (int i=0; i<N; i++) {
+                    ExcessiveWake ew = mExcessiveWake.get(i);
+                    out.writeLong(ew.overTime);
+                    out.writeLong(ew.usedTime);
+                }
+            }
+
+            void readExcessiveWakeFromParcelLocked(Parcel in) {
+                final int N = in.readInt();
+                if (N == 0) {
+                    mExcessiveWake = null;
+                    return;
+                }
+
+                mExcessiveWake = new ArrayList<ExcessiveWake>();
+                for (int i=0; i<N; i++) {
+                    ExcessiveWake ew = new ExcessiveWake();
+                    ew.overTime = in.readLong();
+                    ew.usedTime = in.readLong();
+                    mExcessiveWake.add(ew);
+                }
+            }
+
             void writeToParcelLocked(Parcel out) {
                 out.writeLong(mUserTime);
                 out.writeLong(mSystemTime);
@@ -2648,6 +2829,8 @@
                         out.writeInt(0);
                     }
                 }
+
+                writeExcessiveWakeToParcelLocked(out);
             }
 
             void readFromParcelLocked(Parcel in) {
@@ -2676,6 +2859,8 @@
                         mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
                     }
                 }
+
+                readExcessiveWakeFromParcelLocked(in);
             }
 
             public BatteryStatsImpl getBatteryStats() {
@@ -3153,6 +3338,11 @@
             }
         }
 
+        public class Pid {
+            long mWakeSum;
+            long mWakeStart;
+        }
+
         /**
          * Retrieve the statistics object for a particular process, creating
          * if needed.
@@ -3167,6 +3357,15 @@
             return ps;
         }
 
+        public Pid getPidStatsLocked(int pid) {
+            Pid p = mPids.get(pid);
+            if (p == null) {
+                p = new Pid();
+                mPids.put(pid, p);
+            }
+            return p;
+        }
+
         /**
          * Retrieve the statistics object for a particular service, creating
          * if needed.
@@ -3259,18 +3458,36 @@
             return t;
         }
 
-        public void noteStartWakeLocked(String name, int type) {
+        public void noteStartWakeLocked(int pid, String name, int type) {
             StopwatchTimer t = getWakeTimerLocked(name, type);
             if (t != null) {
                 t.startRunningLocked(BatteryStatsImpl.this);
             }
+            if (pid >= 0) {
+                Pid p = getPidStatsLocked(pid);
+                p.mWakeStart = SystemClock.elapsedRealtime();
+            }
         }
 
-        public void noteStopWakeLocked(String name, int type) {
+        public void noteStopWakeLocked(int pid, String name, int type) {
             StopwatchTimer t = getWakeTimerLocked(name, type);
             if (t != null) {
                 t.stopRunningLocked(BatteryStatsImpl.this);
             }
+            if (pid >= 0) {
+                Pid p = mPids.get(pid);
+                if (p != null) {
+                    p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart;
+                    p.mWakeStart = 0;
+                }
+            }
+        }
+
+        public void reportExcessiveWakeLocked(String proc, long overTime, long usedTime) {
+            Proc p = getProcessStatsLocked(proc);
+            if (p != null) {
+                p.addExcessiveWake(overTime, usedTime);
+            }
         }
         
         public void noteStartSensor(int sensor) {
@@ -3372,6 +3589,10 @@
         return mOnBattery;
     }
 
+    public boolean isScreenOn() {
+        return mScreenOn;
+    }
+
     void initTimes() {
         mBatteryRealtime = mTrackBatteryPastUptime = 0;
         mBatteryUptime = mTrackBatteryPastRealtime = 0;
@@ -3384,24 +3605,24 @@
     public void resetAllStatsLocked() {
         mStartCount = 0;
         initTimes();
-        mScreenOnTimer.reset(false);
+        mScreenOnTimer.reset(this, false);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
-            mScreenBrightnessTimer[i].reset(false);
+            mScreenBrightnessTimer[i].reset(this, false);
         }
         mInputEventCounter.reset(false);
-        mPhoneOnTimer.reset(false);
-        mAudioOnTimer.reset(false);
-        mVideoOnTimer.reset(false);
+        mPhoneOnTimer.reset(this, false);
+        mAudioOnTimer.reset(this, false);
+        mVideoOnTimer.reset(this, false);
         for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
-            mPhoneSignalStrengthsTimer[i].reset(false);
+            mPhoneSignalStrengthsTimer[i].reset(this, false);
         }
-        mPhoneSignalScanningTimer.reset(false);
+        mPhoneSignalScanningTimer.reset(this, false);
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
-            mPhoneDataConnectionsTimer[i].reset(false);
+            mPhoneDataConnectionsTimer[i].reset(this, false);
         }
-        mWifiOnTimer.reset(false);
-        mWifiRunningTimer.reset(false);
-        mBluetoothOnTimer.reset(false);
+        mWifiOnTimer.reset(this, false);
+        mWifiRunningTimer.reset(this, false);
+        mBluetoothOnTimer.reset(this, false);
         
         for (int i=0; i<mUidStats.size(); i++) {
             if (mUidStats.valueAt(i).reset()) {
@@ -4083,6 +4304,7 @@
                 p.mUserTime = p.mLoadedUserTime = in.readLong();
                 p.mSystemTime = p.mLoadedSystemTime = in.readLong();
                 p.mStarts = p.mLoadedStarts = in.readInt();
+                p.readExcessiveWakeFromParcelLocked(in);
             }
 
             NP = in.readInt();
@@ -4271,6 +4493,7 @@
                     out.writeLong(ps.mUserTime);
                     out.writeLong(ps.mSystemTime);
                     out.writeInt(ps.mStarts);
+                    ps.writeExcessiveWakeToParcelLocked(out);
                 }
             }
 
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 2fb481c..4ee89cc 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -151,6 +151,7 @@
     static final int INITIAL_KEYBOARD_BRIGHTNESS = Power.BRIGHTNESS_OFF;
 
     private final int MY_UID;
+    private final int MY_PID;
 
     private boolean mDoneBooting = false;
     private boolean mBootCompleted = false;
@@ -309,7 +310,7 @@
                 long ident = Binder.clearCallingIdentity();
                 try {
                     PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken,
-                            MY_UID, mTag);
+                            MY_UID, MY_PID, mTag);
                     mHeld = true;
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -434,11 +435,11 @@
         }
     }
 
-    PowerManagerService()
-    {
+    PowerManagerService() {
         // Hack to get our uid...  should have a func for this.
         long token = Binder.clearCallingIdentity();
-        MY_UID = Binder.getCallingUid();
+        MY_UID = Process.myUid();
+        MY_PID = Process.myPid();
         Binder.restoreCallingIdentity(token);
 
         // XXX remove this when the kernel doesn't timeout wake locks
@@ -573,13 +574,13 @@
 
     private class WakeLock implements IBinder.DeathRecipient
     {
-        WakeLock(int f, IBinder b, String t, int u) {
+        WakeLock(int f, IBinder b, String t, int u, int p) {
             super();
             flags = f;
             binder = b;
             tag = t;
             uid = u == MY_UID ? Process.SYSTEM_UID : u;
-            pid = Binder.getCallingPid();
+            pid = p;
             if (u != MY_UID || (
                     !"KEEP_SCREEN_ON_FLAG".equals(tag)
                     && !"KeyInputQueue".equals(tag))) {
@@ -631,21 +632,23 @@
 
     public void acquireWakeLock(int flags, IBinder lock, String tag) {
         int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
         if (uid != Process.myUid()) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         }
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLocks) {
-                acquireWakeLockLocked(flags, lock, uid, tag);
+                acquireWakeLockLocked(flags, lock, uid, pid, tag);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, String tag) {
+    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag) {
         int acquireUid = -1;
+        int acquirePid = -1;
         String acquireName = null;
         int acquireType = -1;
 
@@ -657,7 +660,7 @@
         WakeLock wl;
         boolean newlock;
         if (index < 0) {
-            wl = new WakeLock(flags, lock, tag, uid);
+            wl = new WakeLock(flags, lock, tag, uid, pid);
             switch (wl.flags & LOCK_MASK)
             {
                 case PowerManager.FULL_WAKE_LOCK:
@@ -730,13 +733,14 @@
         }
         if (newlock) {
             acquireUid = wl.uid;
+            acquirePid = wl.pid;
             acquireName = wl.tag;
             acquireType = wl.monitorType;
         }
 
         if (acquireType >= 0) {
             try {
-                mBatteryStats.noteStartWakelock(acquireUid, acquireName, acquireType);
+                mBatteryStats.noteStartWakelock(acquireUid, acquirePid, acquireName, acquireType);
             } catch (RemoteException e) {
                 // Ignore
             }
@@ -756,6 +760,7 @@
 
     private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) {
         int releaseUid;
+        int releasePid;
         String releaseName;
         int releaseType;
 
@@ -800,13 +805,14 @@
         // Unlink the lock from the binder.
         wl.binder.unlinkToDeath(wl, 0);
         releaseUid = wl.uid;
+        releasePid = wl.pid;
         releaseName = wl.tag;
         releaseType = wl.monitorType;
 
         if (releaseType >= 0) {
             long origId = Binder.clearCallingIdentity();
             try {
-                mBatteryStats.noteStopWakelock(releaseUid, releaseName, releaseType);
+                mBatteryStats.noteStopWakelock(releaseUid, releasePid, releaseName, releaseType);
             } catch (RemoteException e) {
                 // Ignore
             } finally {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 59deef3..e259887 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -8081,12 +8081,12 @@
                     if (oldHold != newHold) {
                         try {
                             if (oldHold != null) {
-                                mBatteryStats.noteStopWakelock(oldHold.mUid,
+                                mBatteryStats.noteStopWakelock(oldHold.mUid, -1,
                                         "window",
                                         BatteryStats.WAKE_TYPE_WINDOW);
                             }
                             if (newHold != null) {
-                                mBatteryStats.noteStartWakelock(newHold.mUid,
+                                mBatteryStats.noteStartWakelock(newHold.mUid, -1,
                                         "window",
                                         BatteryStats.WAKE_TYPE_WINDOW);
                             }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index dff6a8a..e5d1025 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -199,6 +199,9 @@
     // The minimum amount of time between successive GC requests for a process.
     static final int GC_MIN_INTERVAL = 60*1000;
 
+    // The rate at which we check for apps using excessive wake locks -- 15 mins.
+    static final int WAKE_LOCK_CHECK_DELAY = 15*60*1000;
+
     // How long we allow a receiver to run before giving up on it.
     static final int BROADCAST_TIMEOUT = 10*1000;
 
@@ -770,6 +773,11 @@
     boolean mDidAppSwitch;
     
     /**
+     * Last time (in realtime) at which we checked for wake lock usage.
+     */
+    long mLastWakeLockCheckTime;
+
+    /**
      * Set while we are wanting to sleep, to prevent any
      * activities from being started/resumed.
      */
@@ -914,6 +922,7 @@
     static final int POST_HEAVY_NOTIFICATION_MSG = 24;
     static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
     static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+    static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
 
     AlertDialog mUidAlert;
 
@@ -1173,6 +1182,16 @@
                 } catch (RemoteException e) {
                 }
             } break;
+            case CHECK_EXCESSIVE_WAKE_LOCKS_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    checkExcessiveWakeLocksLocked(true);
+                    mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                    if (mSleeping) {
+                        Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                        mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
+                    }
+                }
+            } break;
             }
         }
     };
@@ -2555,6 +2574,11 @@
 
         mProcDeaths[0]++;
         
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        synchronized (stats) {
+            stats.noteProcessDiedLocked(app.info.uid, pid);
+        }
+
         // Clean up already done if the process has been re-started.
         if (app.pid == pid && app.thread != null &&
                 app.thread.asBinder() == thread.asBinder()) {
@@ -3570,6 +3594,9 @@
             }
             
             if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+                // Start looking for apps that are abusing wake locks.
+                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
                 // Tell anyone interested that we are done booting!
                 SystemProperties.set("sys.boot_completed", "1");
                 broadcastIntentLocked(null, null,
@@ -5375,6 +5402,12 @@
             } else {
                 Slog.w(TAG, "goingToSleep with no resumed activity!");
             }
+
+            // Initialize the wake times of all processes.
+            checkExcessiveWakeLocksLocked(false);
+            mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+            Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+            mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
         }
     }
 
@@ -5424,6 +5457,7 @@
             mWindowManager.setEventDispatching(true);
             mSleeping = false;
             mMainStack.resumeTopActivityLocked(null);
+            mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
         }
     }
 
@@ -11259,6 +11293,52 @@
         }
     }
 
+    final void checkExcessiveWakeLocksLocked(boolean doKills) {
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        if (mLastWakeLockCheckTime == 0) {
+            doKills = false;
+        }
+        if (stats.isScreenOn()) {
+            doKills = false;
+        }
+        final long curRealtime = SystemClock.elapsedRealtime();
+        final long timeSince = curRealtime - mLastWakeLockCheckTime;
+        mLastWakeLockCheckTime = curRealtime;
+        if (timeSince < 5*60*1000) {
+            doKills = false;
+        }
+        int i = mLruProcesses.size();
+        while (i > 0) {
+            i--;
+            ProcessRecord app = mLruProcesses.get(i);
+            if (app.curAdj >= HIDDEN_APP_MIN_ADJ) {
+                long wtime;
+                synchronized (stats) {
+                    wtime = stats.getProcessWakeTime(app.info.uid,
+                            app.pid, curRealtime);
+                }
+                long timeUsed = wtime - app.lastWakeTime;
+                Slog.i(TAG, "Wake for " + app + ": over "
+                        + timeSince + " used " + timeUsed
+                        + " (" + ((timeUsed*100)/timeSince) + "%)");
+                // If a process has held a wake lock for more
+                // than 50% of the time during this period,
+                // that sounds pad.  Kill!
+                if (doKills && timeSince > 0
+                        && ((timeUsed*100)/timeSince) >= 50) {
+                    Slog.i(TAG, "Excessive wake lock in " + app.processName
+                            + " (pid " + app.pid + "): held " + timeUsed
+                            + " during " + timeSince);
+                    EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                            app.processName, app.setAdj, "excessive wake lock");
+                    Process.killProcessQuiet(app.pid);
+                } else {
+                    app.lastWakeTime = wtime;
+                }
+            }
+        }
+    }
+
     private final boolean updateOomAdjLocked(
         ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP) {
         app.hiddenAdj = hiddenAdj;
@@ -11281,6 +11361,12 @@
                     // Likewise do a gc when an app is moving in to the
                     // background (such as a service stopping).
                     scheduleAppGcLocked(app);
+                    // And note its current wake lock time.
+                    BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+                    synchronized (stats) {
+                        app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
+                                app.pid, SystemClock.elapsedRealtime());
+                    }
                 }
                 app.setRawAdj = app.curRawAdj;
             }
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 37da6f7..7314e04 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -93,31 +93,31 @@
         return data;
     }
     
-    public void noteStartWakelock(int uid, String name, int type) {
+    public void noteStartWakelock(int uid, int pid, String name, int type) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.getUidStatsLocked(uid).noteStartWakeLocked(name, type);
+            mStats.noteStartWakeLocked(uid, pid, name, type);
         }
     }
 
-    public void noteStopWakelock(int uid, String name, int type) {
+    public void noteStopWakelock(int uid, int pid, String name, int type) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.getUidStatsLocked(uid).noteStopWakeLocked(name, type);
+            mStats.noteStopWakeLocked(uid, pid, name, type);
         }
     }
 
     public void noteStartSensor(int uid, int sensor) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.getUidStatsLocked(uid).noteStartSensor(sensor);
+            mStats.noteStartSensorLocked(uid, sensor);
         }
     }
     
     public void noteStopSensor(int uid, int sensor) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.getUidStatsLocked(uid).noteStopSensor(sensor);
+            mStats.noteStopSensorLocked(uid, sensor);
         }
     }
     
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 18b1acb..73e489f 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -74,6 +74,7 @@
     Bundle instrumentationArguments;// as given to us
     ComponentName instrumentationResultClass;// copy of instrumentationClass
     BroadcastRecord curReceiver;// receiver currently running in the app
+    long lastWakeTime;          // How long proc held wake lock at last check
     long lastRequestedGc;       // When we last asked the app to do a gc
     long lastLowMemory;         // When we last told the app that memory is low
     boolean reportLowMemory;    // Set to true when waiting to report low mem
@@ -158,7 +159,7 @@
         pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting=");
                 pw.print(starting); pw.print(" lastPss="); pw.println(lastPss);
         pw.print(prefix); pw.print("lastActivityTime="); pw.print(lastActivityTime);
-                pw.print(" lruWeight="); pw.println(lruWeight);
+                pw.print(" lruWeight="); pw.print(lruWeight);
                 pw.print(" hidden="); pw.print(hidden);
                 pw.print(" empty="); pw.println(empty);
         pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
@@ -177,6 +178,10 @@
                 pw.print(" persistentActivities="); pw.println(persistentActivities);
         pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
                 pw.print(" lruSeq="); pw.println(lruSeq);
+        pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
+                pw.print(" lastRequestedGc="); pw.print(lastRequestedGc);
+                pw.print(" lastLowMemory="); pw.print(lastLowMemory);
+                pw.print(" reportLowMemory="); pw.println(reportLowMemory);
         if (killedBackground) {
             pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground);
         }
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 75365ad..35f18750 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -149,7 +149,9 @@
                     pw.print(" foregroundId="); pw.print(foregroundId);
                     pw.print(" foregroundNoti="); pw.println(foregroundNoti);
         }
-        pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity-now);
+        pw.print(prefix); pw.print("createTime=");
+                pw.print(createTime-SystemClock.elapsedRealtime());
+                pw.print(" lastActivity="); pw.print(lastActivity-now);
                 pw.print(" executingStart="); pw.print(executingStart-now);
                 pw.print(" restartTime="); pw.println(restartTime);
         if (startRequested || lastStartId != 0) {
@@ -213,7 +215,8 @@
         dataDir = sInfo.applicationInfo.dataDir;
         exported = sInfo.exported;
         this.restarter = restarter;
-        createTime = lastActivity = SystemClock.uptimeMillis();
+        createTime = SystemClock.elapsedRealtime();
+        lastActivity = SystemClock.uptimeMillis();
     }
 
     public AppBindRecord retrieveAppBindingLocked(Intent intent,
diff --git a/tests/BatteryWaster/res/layout/battery_waster.xml b/tests/BatteryWaster/res/layout/battery_waster.xml
index e1cb6bf..36aa68b 100644
--- a/tests/BatteryWaster/res/layout/battery_waster.xml
+++ b/tests/BatteryWaster/res/layout/battery_waster.xml
@@ -30,6 +30,16 @@
         android:text="@string/waste_away"
         />
 
+    <CheckBox android:id="@+id/checkbox_wake"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="25dp"
+        android:layout_marginTop="25dp"
+        android:textSize="18sp"
+        android:textColor="#ffffffff"
+        android:text="@string/wake_away"
+        />
+
     <ScrollView android:id="@+id/scroll"
         android:layout_width="match_parent"
         android:layout_height="0px"
diff --git a/tests/BatteryWaster/res/values/strings.xml b/tests/BatteryWaster/res/values/strings.xml
index 46c5fa1..a3b849a 100644
--- a/tests/BatteryWaster/res/values/strings.xml
+++ b/tests/BatteryWaster/res/values/strings.xml
@@ -18,5 +18,7 @@
 
     <string name="waste_away">Discharge my battery!</string>
 
+    <string name="wake_away">Keep my device awake!</string>
+
 </resources>
 
diff --git a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
index 8ea7e00..499330f 100644
--- a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
+++ b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
@@ -41,6 +41,8 @@
     PowerManager.WakeLock mWakeLock;
     SpinThread mThread;
 
+    boolean mWasting, mWaking;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -50,6 +52,7 @@
         setContentView(R.layout.battery_waster);
 
         findViewById(R.id.checkbox).setOnClickListener(mClickListener);
+        findViewById(R.id.checkbox_wake).setOnClickListener(mWakeClickListener);
         mLog = (TextView)findViewById(R.id.log);
 
         mDateFormat = DateFormat.getInstance();
@@ -67,9 +70,18 @@
 
     @Override
     public void onPause() {
+        super.onPause();
         stopRunning();
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+    }
+
     View.OnClickListener mClickListener = new View.OnClickListener() {
         public void onClick(View v) {
             CheckBox checkbox = (CheckBox)v;
@@ -81,23 +93,54 @@
         }
     };
 
+    View.OnClickListener mWakeClickListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            CheckBox checkbox = (CheckBox)v;
+            if (checkbox.isChecked()) {
+                mWaking = true;
+                updateWakeLock();
+            } else {
+                mWaking = false;
+                updateWakeLock();
+            }
+        }
+    };
+
     void startRunning() {
-        log("Start");
-        registerReceiver(mReceiver, mFilter);
-        mWakeLock.acquire();
-        if (mThread == null) {
-            mThread = new SpinThread();
-            mThread.start();
+        if (!mWasting) {
+            log("Start");
+            registerReceiver(mReceiver, mFilter);
+            mWasting = true;
+            updateWakeLock();
+            if (mThread == null) {
+                mThread = new SpinThread();
+                mThread.start();
+            }
         }
     }
 
     void stopRunning() {
-        log("Stop");
-        unregisterReceiver(mReceiver);
-        mWakeLock.release();
-        if (mThread != null) {
-            mThread.quit();
-            mThread = null;
+        if (mWasting) {
+            log("Stop");
+            unregisterReceiver(mReceiver);
+            mWasting = false;
+            updateWakeLock();
+            if (mThread != null) {
+                mThread.quit();
+                mThread = null;
+            }
+        }
+    }
+
+    void updateWakeLock() {
+        if (mWasting || mWaking) {
+            if (!mWakeLock.isHeld()) {
+                mWakeLock.acquire();
+            }
+        } else {
+            if (mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
         }
     }