Track client requests through location manager.

This fixes a problem where applications could ask the location
manager to do very heavy-weight things (like...  say... update
location every minute), which would get accounted against the
system instead of the application because ultimately it is the
system making the heavy calls (wake locks, etc).

To solve this, we introduce a new class WorkSource representing
the source of some work.  Wake locks and Wifi locks allow you
to set the source to use (but only if you are system code and thus
can get the permission to do so), which is what will be reported
to the battery stats until the actual caller.

For the initial implementation, the location manager keeps track
of all clients requesting periodic updates, and tells its providers
about them as a WorkSource param when setting their min update time.
The network location provider uses this to set the source on the
wake and wifi locks it acquires, when doing work because of the
update period.

This should also be used elsewhere, such as in the GPS provider,
but this is a good start.

Change-Id: I2b6ffafad9e90ecf15d7c502e2db675fd52ae3cf
diff --git a/api/current.xml b/api/current.xml
index d2e1f05..d1dfc09 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -84326,6 +84326,8 @@
 >
 <parameter name="uid" type="int">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onDisable"
  return="void"
@@ -84455,6 +84457,8 @@
 >
 <parameter name="uid" type="int">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onRequiresCell"
  return="boolean"
@@ -84516,6 +84520,8 @@
 >
 <parameter name="minTime" type="long">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onSupportsAltitude"
  return="boolean"
@@ -101636,6 +101642,19 @@
 <parameter name="refCounted" type="boolean">
 </parameter>
 </method>
+<method name="setWorkSource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
+</method>
 </class>
 </package>
 <package name="android.opengl"
@@ -128447,6 +128466,19 @@
 <parameter name="value" type="boolean">
 </parameter>
 </method>
+<method name="setWorkSource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
+</method>
 </class>
 <class name="Process"
  extends="java.lang.Object"
@@ -129595,6 +129627,134 @@
 </parameter>
 </method>
 </class>
+<class name="WorkSource"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="WorkSource"
+ type="android.os.WorkSource"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="WorkSource"
+ type="android.os.WorkSource"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="orig" type="android.os.WorkSource">
+</parameter>
+</constructor>
+<method name="add"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="clear"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="diff"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="remove"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="set"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 </package>
 <package name="android.os.storage"
 >
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index d3ec3d9..e1d6619 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -64,7 +64,7 @@
                             = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
                     try {
                         IBinder lock = new Binder();
-                        pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power");
+                        pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power", null);
                         pm.setStayOnSetting(val);
                         pm.releaseWakeLock(lock, 0);
                     }
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index d0b67cc..3e44fcf 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -45,6 +45,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.text.format.Time;
@@ -126,8 +127,8 @@
 
     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
 
-    private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
-    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
+    private static final String SYNC_WAKE_LOCK = "*sync*";
+    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
 
     private Context mContext;
 
@@ -1695,6 +1696,7 @@
                 Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
             }
             mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+            mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
             if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
                 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
                 mActiveSyncContext.close();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index f5b1e57..f182a7a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -404,6 +404,7 @@
         
         public static final byte CMD_UPDATE = 0;
         public static final byte CMD_START = 1;
+        public static final byte CMD_OVERFLOW = 2;
         
         public byte cmd;
         
@@ -1703,6 +1704,8 @@
                 pw.print(" ");
                 if (rec.cmd == HistoryItem.CMD_START) {
                     pw.println(" START");
+                } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+                    pw.println(" *OVERFLOW*");
                 } else {
                     if (rec.batteryLevel < 10) pw.print("00");
                     else if (rec.batteryLevel < 100) pw.print("0");
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 01cc408..0067e940 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -17,10 +17,13 @@
 
 package android.os;
 
+import android.os.WorkSource;
+
 /** @hide */
 interface IPowerManager
 {
-    void acquireWakeLock(int flags, IBinder lock, String tag);
+    void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws);
+    void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
     void goToSleep(long time);
     void goToSleepWithReason(long time, int reason);
     void releaseWakeLock(IBinder lock, int flags);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index f4ca8bc..3876a3e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -209,6 +209,7 @@
         int mCount = 0;
         boolean mRefCounted = true;
         boolean mHeld = false;
+        WorkSource mWorkSource;
 
         WakeLock(int flags, String tag)
         {
@@ -247,7 +248,7 @@
             synchronized (mToken) {
                 if (!mRefCounted || mCount++ == 0) {
                     try {
-                        mService.acquireWakeLock(mFlags, mToken, mTag);
+                        mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
                     } catch (RemoteException e) {
                     }
                     mHeld = true;
@@ -313,6 +314,32 @@
             }
         }
 
+        public void setWorkSource(WorkSource ws) {
+            synchronized (mToken) {
+                if (ws != null && ws.size() == 0) {
+                    ws = null;
+                }
+                boolean changed = true;
+                if (ws == null) {
+                    mWorkSource = null;
+                } else if (mWorkSource == null) {
+                    changed = mWorkSource != null;
+                    mWorkSource = new WorkSource(ws);
+                } else {
+                    changed = mWorkSource.diff(ws);
+                    if (changed) {
+                        mWorkSource.set(ws);
+                    }
+                }
+                if (changed && mHeld) {
+                    try {
+                        mService.updateWakeLockWorkSource(mToken, mWorkSource);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+
         public String toString() {
             synchronized (mToken) {
                 return "WakeLock{"
diff --git a/core/java/android/os/WorkSource.aidl b/core/java/android/os/WorkSource.aidl
new file mode 100644
index 0000000..1e7fabc
--- /dev/null
+++ b/core/java/android/os/WorkSource.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2010, 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 android.os;
+
+parcelable WorkSource;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
new file mode 100644
index 0000000..bba1984
--- /dev/null
+++ b/core/java/android/os/WorkSource.java
@@ -0,0 +1,311 @@
+package android.os;
+
+/**
+ * Describes the source of some work that may be done by someone else.
+ * Currently the public representation of what a work source is is not
+ * defined; this is an opaque container.
+ */
+public class WorkSource implements Parcelable {
+    int mNum;
+    int[] mUids;
+
+    /**
+     * Internal statics to avoid object allocations in some operations.
+     * The WorkSource object itself is not thread safe, but we need to
+     * hold sTmpWorkSource lock while working with these statics.
+     */
+    static final WorkSource sTmpWorkSource = new WorkSource(0);
+    /**
+     * For returning newbie work from a modification operation.
+     */
+    static WorkSource sNewbWork;
+    /**
+     * For returning gone work form a modification operation.
+     */
+    static WorkSource sGoneWork;
+
+    /**
+     * Create an empty work source.
+     */
+    public WorkSource() {
+        mNum = 0;
+    }
+
+    /**
+     * Create a new WorkSource that is a copy of an existing one.
+     * If <var>orig</var> is null, an empty WorkSource is created.
+     */
+    public WorkSource(WorkSource orig) {
+        if (orig == null) {
+            mNum = 0;
+            return;
+        }
+        mNum = orig.mNum;
+        if (orig.mUids != null) {
+            mUids = orig.mUids.clone();
+        } else {
+            mUids = null;
+        }
+    }
+
+    /** @hide */
+    public WorkSource(int uid) {
+        mNum = 1;
+        mUids = new int[] { uid, 0 };
+    }
+
+    WorkSource(Parcel in) {
+        mNum = in.readInt();
+        mUids = in.createIntArray();
+    }
+
+    /** @hide */
+    public int size() {
+        return mNum;
+    }
+
+    /** @hide */
+    public int get(int index) {
+        return mUids[index];
+    }
+
+    /**
+     * Clear this WorkSource to be empty.
+     */
+    public void clear() {
+        mNum = 0;
+    }
+
+    /**
+     * Compare this WorkSource with another.
+     * @param other The WorkSource to compare against.
+     * @return If there is a difference, true is returned.
+     */
+    public boolean diff(WorkSource other) {
+        int N = mNum;
+        if (N != other.mNum) {
+            return true;
+        }
+        final int[] uids1 = mUids;
+        final int[] uids2 = other.mUids;
+        for (int i=0; i<N; i++) {
+            if (uids1[i] != uids2[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Replace the current contents of this work source with the given
+     * work source.  If <var>other</var> is null, the current work source
+     * will be made empty.
+     */
+    public void set(WorkSource other) {
+        if (other == null) {
+            mNum = 0;
+            return;
+        }
+        mNum = other.mNum;
+        if (other.mUids != null) {
+            if (mUids != null && mUids.length >= mNum) {
+                System.arraycopy(other.mUids, 0, mUids, 0, mNum);
+            } else {
+                mUids = other.mUids.clone();
+            }
+        } else {
+            mUids = null;
+        }
+    }
+
+    /** @hide */
+    public void set(int uid) {
+        mNum = 1;
+        if (mUids == null) mUids = new int[2];
+        mUids[0] = uid;
+    }
+
+    /** @hide */
+    public WorkSource[] setReturningDiffs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            sGoneWork = null;
+            updateLocked(other, true, true);
+            if (sNewbWork != null || sGoneWork != null) {
+                WorkSource[] diffs = new WorkSource[2];
+                diffs[0] = sNewbWork;
+                diffs[1] = sGoneWork;
+                return diffs;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Merge the contents of <var>other</var> WorkSource in to this one.
+     *
+     * @param other The other WorkSource whose contents are to be merged.
+     * @return Returns true if any new sources were added.
+     */
+    public boolean add(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            return updateLocked(other, false, false);
+        }
+    }
+
+    /** @hide */
+    public WorkSource addReturningNewbs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            updateLocked(other, false, true);
+            return sNewbWork;
+        }
+    }
+
+    /** @hide */
+    public boolean add(int uid) {
+        synchronized (sTmpWorkSource) {
+            sTmpWorkSource.mUids[0] = uid;
+            return updateLocked(sTmpWorkSource, false, false);
+        }
+    }
+
+    /** @hide */
+    public WorkSource addReturningNewbs(int uid) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            sTmpWorkSource.mUids[0] = uid;
+            updateLocked(sTmpWorkSource, false, true);
+            return sNewbWork;
+        }
+    }
+
+    public boolean remove(WorkSource other) {
+        int N1 = mNum;
+        final int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0;
+        for (int i2=0; i2<N2 && i1<N1; i2++) {
+            if (uids2[i2] == uids1[i1]) {
+                N1--;
+                if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1-1, N1-i1);
+            }
+            while (i1 < N1 && uids2[i2] > uids1[i1]) {
+                i1++;
+            }
+        }
+
+        mNum = N1;
+
+        return changed;
+    }
+
+    private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+        int N1 = mNum;
+        int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0;
+        for (int i2=0; i2<N2; i2++) {
+            if (i1 >= N1 || uids2[i2] < uids1[i1]) {
+                // Need to insert a new uid.
+                changed = true;
+                if (uids1 == null) {
+                    uids1 = new int[4];
+                    uids1[0] = uids2[i2];
+                } else if (i1 >= uids1.length) {
+                    int[] newuids = new int[(uids1.length*3)/2];
+                    if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
+                    if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
+                    uids1 = newuids;
+                    uids1[i1] = uids2[i2];
+                } else {
+                    if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
+                    uids1[i1] = uids2[i2];
+                }
+                if (returnNewbs) {
+                    if (sNewbWork == null) {
+                        sNewbWork = new WorkSource(uids2[i2]);
+                    } else {
+                        sNewbWork.addLocked(uids2[i2]);
+                    }
+                }
+                N1++;
+                i1++;
+            } else {
+                if (!set) {
+                    // Skip uids that already exist or are not in 'other'.
+                    do {
+                        i1++;
+                    } while (i1 < N1 && uids2[i2] >= uids1[i1]);
+                } else {
+                    // Remove any uids that don't exist in 'other'.
+                    int start = i1;
+                    while (i1 < N1 && uids2[i2] > uids1[i1]) {
+                        if (sGoneWork == null) {
+                            sGoneWork = new WorkSource(uids1[i1]);
+                        } else {
+                            sGoneWork.addLocked(uids1[i1]);
+                        }
+                        i1++;
+                    }
+                    if (start < i1) {
+                        System.arraycopy(uids1, i1, uids1, start, i1-start);
+                        N1 -= i1-start;
+                        i1 = start;
+                    }
+                    // If there is a matching uid, skip it.
+                    if (i1 < N1 && uids2[i1] == uids1[i1]) {
+                        i1++;
+                    }
+                }
+            }
+        }
+
+        mNum = N1;
+        mUids = uids1;
+
+        return changed;
+    }
+
+    private void addLocked(int uid) {
+        if (mUids == null) {
+            mUids = new int[4];
+            mUids[0] = uid;
+            mNum = 1;
+            return;
+        }
+        if (mNum >= mUids.length) {
+            int[] newuids = new int[(mNum*3)/2];
+            System.arraycopy(mUids, 0, newuids, 0, mNum);
+            mUids = newuids;
+        }
+
+        mUids[mNum] = uid;
+        mNum++;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNum);
+        dest.writeIntArray(mUids);
+    }
+
+    public static final Parcelable.Creator<WorkSource> CREATOR
+            = new Parcelable.Creator<WorkSource>() {
+        public WorkSource createFromParcel(Parcel in) {
+            return new WorkSource(in);
+        }
+        public WorkSource[] newArray(int size) {
+            return new WorkSource[size];
+        }
+    };
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1620778..bd87a0d 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.BatteryStatsImpl;
 
+import android.os.WorkSource;
 import android.telephony.SignalStrength;
 
 interface IBatteryStats {
@@ -33,6 +34,9 @@
        SensorService.cpp */
     void noteStopSensor(int uid, int sensor);
 
+    void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+    void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+
     void noteStartGps(int uid);
     void noteStopGps(int uid);
     void noteScreenOn();
@@ -57,6 +61,12 @@
     void noteScanWifiLockReleased(int uid);
     void noteWifiMulticastEnabled(int uid);
     void noteWifiMulticastDisabled(int uid);
+    void noteFullWifiLockAcquiredFromSource(in WorkSource ws);
+    void noteFullWifiLockReleasedFromSource(in WorkSource ws);
+    void noteScanWifiLockAcquiredFromSource(in WorkSource ws);
+    void noteScanWifiLockReleasedFromSource(in WorkSource ws);
+    void noteWifiMulticastEnabledFromSource(in WorkSource ws);
+    void noteWifiMulticastDisabledFromSource(in WorkSource ws);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0ef1ea5..753dbf0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -68,12 +69,12 @@
     private static final int VERSION = 50;
 
     // Maximum number of items we will record in the history.
-    private static final int MAX_HISTORY_ITEMS = 1000;
+    private static final int MAX_HISTORY_ITEMS = 2000;
     
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
     // in to one common name.
-    private static final int MAX_WAKELOCKS_PER_UID = 20;
+    private static final int MAX_WAKELOCKS_PER_UID = 30;
     
     private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
     
@@ -1171,7 +1172,7 @@
         // 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)) {
+                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) {
             // 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
@@ -1187,6 +1188,10 @@
             return;
         }
 
+        if (mNumHistoryItems == MAX_HISTORY_ITEMS) {
+            addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW);
+        }
+
         if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
             // Once we've reached the maximum number of items, we only
             // record changes to the battery level.
@@ -1327,6 +1332,20 @@
         }
     }
 
+    public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStartWakeLocked(ws.get(i), pid, name, type);
+        }
+    }
+
+    public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStopWakeLocked(ws.get(i), pid, name, type);
+        }
+    }
+
     public int startAddingCpuLocked() {
         mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
 
@@ -1947,6 +1966,48 @@
         getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
     }
 
+    public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockAcquiredLocked(ws.get(i));
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockReleasedLocked(ws.get(i));
+        }
+    }
+
+    public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteScanWifiLockAcquiredLocked(ws.get(i));
+        }
+    }
+
+    public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteScanWifiLockReleasedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastEnabledLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastDisabledLocked(ws.get(i));
+        }
+    }
+
     @Override public long getScreenOnTime(long batteryRealtime, int which) {
         return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a9e49713..9b9b4be 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -930,7 +930,7 @@
     <permission android:name="android.permission.UPDATE_DEVICE_STATS"
         android:label="@string/permlab_batteryStats"
         android:description="@string/permdesc_batteryStats"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signatureOrSystem" />
 
     <!-- Allows an application to open windows that are for use by parts
          of the system user interface.  Not for use by third party apps. -->
diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl
index 2b9782a..ecf6789 100644
--- a/location/java/android/location/ILocationProvider.aidl
+++ b/location/java/android/location/ILocationProvider.aidl
@@ -20,6 +20,7 @@
 import android.location.Location;
 import android.net.NetworkInfo;
 import android.os.Bundle;
+import android.os.WorkSource;
 
 /**
  * Binder interface for services that implement location providers.
@@ -43,7 +44,7 @@
     long getStatusUpdateTime();
     String getInternalState();
     void enableLocationTracking(boolean enable);
-    void setMinTime(long minTime);
+    void setMinTime(long minTime, in WorkSource ws);
     void updateNetworkState(int state, in NetworkInfo info);
     void updateLocation(in Location location);
     boolean sendExtraCommand(String command, inout Bundle extras);
diff --git a/location/java/android/location/provider/LocationProvider.java b/location/java/android/location/provider/LocationProvider.java
index cf939de..95b4425 100644
--- a/location/java/android/location/provider/LocationProvider.java
+++ b/location/java/android/location/provider/LocationProvider.java
@@ -26,6 +26,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.util.Log;
 
 /**
@@ -106,8 +107,8 @@
             LocationProvider.this.onEnableLocationTracking(enable);
         }
 
-        public void setMinTime(long minTime) {
-            LocationProvider.this.onSetMinTime(minTime);
+        public void setMinTime(long minTime, WorkSource ws) {
+            LocationProvider.this.onSetMinTime(minTime, ws);
         }
 
         public void updateNetworkState(int state, NetworkInfo info) {
@@ -123,11 +124,11 @@
         }
 
         public void addListener(int uid) {
-            LocationProvider.this.onAddListener(uid);
+            LocationProvider.this.onAddListener(uid, new WorkSource(uid));
         }
 
         public void removeListener(int uid) {
-            LocationProvider.this.onRemoveListener(uid);
+            LocationProvider.this.onRemoveListener(uid, new WorkSource(uid));
         }
     };
 
@@ -303,8 +304,9 @@
      * the frequency of updates to match the requested frequency.
      *
      * @param minTime the smallest minTime value over all listeners for this provider.
+     * @param ws the source this work is coming from.
      */
-    public abstract void onSetMinTime(long minTime);
+    public abstract void onSetMinTime(long minTime, WorkSource ws);
 
     /**
      * Updates the network state for the given provider. This function must
@@ -340,14 +342,16 @@
      * Notifies the location provider when a new client is listening for locations.
      *
      * @param uid user ID of the new client.
+     * @param ws a WorkSource representation of the client.
      */
-    public abstract void onAddListener(int uid);
+    public abstract void onAddListener(int uid, WorkSource ws);
 
     /**
      * Notifies the location provider when a client is no longer listening for locations.
      *
      * @param uid user ID of the client no longer listening.
+     * @param ws a WorkSource representation of the client.
      */
-    public abstract void onRemoveListener(int uid);
+    public abstract void onRemoveListener(int uid, WorkSource ws);
 }
 
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index e454c08..aa87f29 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -54,6 +54,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
@@ -504,7 +505,7 @@
         parseLeftoverJournals();
 
         // Power management
-        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup");
+        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
 
         // Start the backup passes going
         setBackupEnabled(areEnabled);
@@ -1363,6 +1364,7 @@
                         ? IApplicationThread.BACKUP_MODE_FULL
                         : IApplicationThread.BACKUP_MODE_INCREMENTAL;
                 try {
+                    mWakelock.setWorkSource(new WorkSource(request.appInfo.uid));
                     agent = bindToAgentSynchronous(request.appInfo, mode);
                     if (agent != null) {
                         int result = processOneBackup(request, agent, transport);
@@ -1378,6 +1380,8 @@
                 }
             }
 
+            mWakelock.setWorkSource(null);
+
             return BackupConstants.TRANSPORT_OK;
         }
 
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index a38970f..f3ce8ba 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -52,6 +52,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -157,6 +158,12 @@
     private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider =
         new HashMap<String,ArrayList<UpdateRecord>>();
 
+    /**
+     * Temporary filled in when computing min time for a provider.  Access is
+     * protected by global lock mLock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
     // Proximity listeners
     private Receiver mProximityReceiver = null;
     private ILocationListener mProximityListener = null;
@@ -912,7 +919,7 @@
         if (enabled) {
             p.enable();
             if (listeners > 0) {
-                p.setMinTime(getMinTimeLocked(provider));
+                p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource);
                 p.enableLocationTracking(true);
             }
         } else {
@@ -924,9 +931,21 @@
     private long getMinTimeLocked(String provider) {
         long minTime = Long.MAX_VALUE;
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        mTmpWorkSource.clear();
         if (records != null) {
             for (int i=records.size()-1; i>=0; i--) {
-                minTime = Math.min(minTime, records.get(i).mMinTime);
+                UpdateRecord ur = records.get(i);
+                long curTime = ur.mMinTime;
+                if (curTime < minTime) {
+                    minTime = curTime;
+                }
+            }
+            long inclTime = (minTime*3)/2;
+            for (int i=records.size()-1; i>=0; i--) {
+                UpdateRecord ur = records.get(i);
+                if (ur.mMinTime <= inclTime) {
+                    mTmpWorkSource.add(ur.mUid);
+                }
             }
         }
         return minTime;
@@ -1124,7 +1143,7 @@
             boolean isProviderEnabled = isAllowedBySettingsLocked(provider);
             if (isProviderEnabled) {
                 long minTimeForProvider = getMinTimeLocked(provider);
-                p.setMinTime(minTimeForProvider);
+                p.setMinTime(minTimeForProvider, mTmpWorkSource);
                 // try requesting single shot if singleShot is true, and fall back to
                 // regular location tracking if requestSingleShotFix() is not supported
                 if (!singleShot || !p.requestSingleShotFix()) {
@@ -1222,7 +1241,7 @@
                 LocationProviderInterface p = mProvidersByName.get(provider);
                 if (p != null) {
                     if (hasOtherListener) {
-                        p.setMinTime(getMinTimeLocked(provider));
+                        p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource);
                     } else {
                         p.enableLocationTracking(false);
                     }
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 16f3f10..af8d7d4 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -50,6 +50,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Settings;
 import android.util.EventLog;
@@ -310,7 +311,7 @@
                 long ident = Binder.clearCallingIdentity();
                 try {
                     PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken,
-                            MY_UID, MY_PID, mTag);
+                            MY_UID, MY_PID, mTag, null);
                     mHeld = true;
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -607,6 +608,7 @@
         final int uid;
         final int pid;
         final int monitorType;
+        WorkSource ws;
         boolean activated = true;
         int minState;
     }
@@ -630,35 +632,74 @@
                 || n == PowerManager.SCREEN_DIM_WAKE_LOCK;
     }
 
-    public void acquireWakeLock(int flags, IBinder lock, String tag) {
+    void enforceWakeSourcePermission(int uid, int pid) {
+        if (uid == Process.myUid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                pid, uid, null);
+    }
+
+    public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) {
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
         if (uid != Process.myUid()) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLocks) {
-                acquireWakeLockLocked(flags, lock, uid, pid, tag);
+                acquireWakeLockLocked(flags, lock, uid, pid, tag, ws);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    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;
+    void noteStartWakeLocked(WakeLock wl, WorkSource ws) {
+        try {
+            if (ws != null) {
+                mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag,
+                        wl.monitorType);
+            } else {
+                mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
+            }
+        } catch (RemoteException e) {
+            // Ignore
+        }
+    }
 
+    void noteStopWakeLocked(WakeLock wl, WorkSource ws) {
+        try {
+            if (ws != null) {
+                mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag,
+                        wl.monitorType);
+            } else {
+                mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
+            }
+        } catch (RemoteException e) {
+            // Ignore
+        }
+    }
+
+    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag,
+            WorkSource ws) {
         if (mSpew) {
             Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag);
         }
 
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+
         int index = mLocks.getIndex(lock);
         WakeLock wl;
         boolean newlock;
+        boolean diffsource;
+        WorkSource oldsource;
         if (index < 0) {
             wl = new WakeLock(flags, lock, tag, uid, pid);
             switch (wl.flags & LOCK_MASK)
@@ -687,10 +728,31 @@
                     return;
             }
             mLocks.addLock(wl);
+            if (ws != null) {
+                wl.ws = new WorkSource(ws);
+            }
             newlock = true;
+            diffsource = false;
+            oldsource = null;
         } else {
             wl = mLocks.get(index);
             newlock = false;
+            oldsource = wl.ws;
+            if (oldsource != null) {
+                if (ws == null) {
+                    wl.ws = null;
+                    diffsource = true;
+                } else {
+                    diffsource = oldsource.diff(ws);
+                }
+            } else if (ws != null) {
+                diffsource = true;
+            } else {
+                diffsource = false;
+            }
+            if (diffsource) {
+                wl.ws = new WorkSource(ws);
+            }
         }
         if (isScreenLock(flags)) {
             // if this causes a wakeup, we reactivate all of the locks and
@@ -731,19 +793,41 @@
                 enableProximityLockLocked();
             }
         }
-        if (newlock) {
-            acquireUid = wl.uid;
-            acquirePid = wl.pid;
-            acquireName = wl.tag;
-            acquireType = wl.monitorType;
-        }
 
-        if (acquireType >= 0) {
-            try {
-                mBatteryStats.noteStartWakelock(acquireUid, acquirePid, acquireName, acquireType);
-            } catch (RemoteException e) {
-                // Ignore
+        if (diffsource) {
+            // If the lock sources have changed, need to first release the
+            // old ones.
+            noteStopWakeLocked(wl, oldsource);
+        }
+        if (newlock || diffsource) {
+            noteStartWakeLocked(wl, ws);
+        }
+    }
+
+    public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLocks) {
+                int index = mLocks.getIndex(lock);
+                if (index < 0) {
+                    throw new IllegalArgumentException("Wake lock not active");
+                }
+                WakeLock wl = mLocks.get(index);
+                WorkSource oldsource = wl.ws;
+                wl.ws = ws != null ? new WorkSource(ws) : null;
+                noteStopWakeLocked(wl, oldsource);
+                noteStartWakeLocked(wl, ws);
             }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -759,11 +843,6 @@
     }
 
     private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) {
-        int releaseUid;
-        int releasePid;
-        String releaseName;
-        int releaseType;
-
         WakeLock wl = mLocks.removeLock(lock);
         if (wl == null) {
             return;
@@ -804,17 +883,11 @@
         }
         // 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) {
+        if (wl.monitorType >= 0) {
             long origId = Binder.clearCallingIdentity();
             try {
-                mBatteryStats.noteStopWakelock(releaseUid, releasePid, releaseName, releaseType);
-            } catch (RemoteException e) {
-                // Ignore
+                noteStopWakeLocked(wl, wl.ws);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index 2e7e3e1..f0b59555 100755
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.Binder;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.util.Slog;
 
 import java.util.LinkedList;
@@ -39,6 +40,7 @@
 
     private final LinkedList<Vibration> mVibrations;
     private Vibration mCurrentVibration;
+    private final WorkSource mTmpWorkSource = new WorkSource();
 
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
@@ -46,22 +48,24 @@
         private final long    mStartTime;
         private final long[]  mPattern;
         private final int     mRepeat;
+        private final int     mUid;
 
-        Vibration(IBinder token, long millis) {
-            this(token, millis, null, 0);
+        Vibration(IBinder token, long millis, int uid) {
+            this(token, millis, null, 0, uid);
         }
 
-        Vibration(IBinder token, long[] pattern, int repeat) {
-            this(token, 0, pattern, repeat);
+        Vibration(IBinder token, long[] pattern, int repeat, int uid) {
+            this(token, 0, pattern, repeat, uid);
         }
 
         private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat) {
+                int repeat, int uid) {
             mToken = token;
             mTimeout = millis;
             mStartTime = SystemClock.uptimeMillis();
             mPattern = pattern;
             mRepeat = repeat;
+            mUid = uid;
         }
 
         public void binderDied() {
@@ -98,7 +102,7 @@
         mContext = context;
         PowerManager pm = (PowerManager)context.getSystemService(
                 Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
         mVibrations = new LinkedList<Vibration>();
@@ -113,6 +117,7 @@
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        int uid = Binder.getCallingUid();
         // We're running in the system server so we cannot crash. Check for a
         // timeout of 0 or negative. This will ensure that a vibration has
         // either a timeout of > 0 or a non-null pattern.
@@ -122,7 +127,7 @@
             // longer than milliseconds.
             return;
         }
-        Vibration vib = new Vibration(token, milliseconds);
+        Vibration vib = new Vibration(token, milliseconds, uid);
         synchronized (mVibrations) {
             removeVibrationLocked(token);
             doCancelVibrateLocked();
@@ -146,6 +151,7 @@
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        int uid = Binder.getCallingUid();
         // so wakelock calls will succeed
         long identity = Binder.clearCallingIdentity();
         try {
@@ -165,7 +171,7 @@
                 return;
             }
 
-            Vibration vib = new Vibration(token, pattern, repeat);
+            Vibration vib = new Vibration(token, pattern, repeat, uid);
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
@@ -280,6 +286,8 @@
 
         VibrateThread(Vibration vib) {
             mVibration = vib;
+            mTmpWorkSource.set(vib.mUid);
+            mWakeLock.setWorkSource(mTmpWorkSource);
             mWakeLock.acquire();
         }
 
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 0eca082..f11c0f7 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -63,6 +63,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Slog;
 import android.text.TextUtils;
@@ -2036,8 +2037,8 @@
     }
 
     private class WifiLock extends DeathRecipient {
-        WifiLock(int lockMode, String tag, IBinder binder) {
-            super(lockMode, tag, binder);
+        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+            super(lockMode, tag, binder, ws);
         }
 
         public void binderDied() {
@@ -2111,7 +2112,15 @@
         }
     }
 
-    public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) {
+    void enforceWakeSourcePermission(int uid, int pid) {
+        if (uid == Process.myUid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                pid, uid, null);
+    }
+
+    public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         if (lockMode != WifiManager.WIFI_MODE_FULL &&
                 lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
@@ -2120,34 +2129,68 @@
             if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
             return false;
         }
-        WifiLock wifiLock = new WifiLock(lockMode, tag, binder);
+        if (ws != null) {
+            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
+        }
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws == null) {
+            ws = new WorkSource(Binder.getCallingUid());
+        }
+        WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
         synchronized (mLocks) {
             return acquireWifiLockLocked(wifiLock);
         }
     }
 
+    private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                /* Treat high power as a full lock for battery stats */
+                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
+    private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                /* Treat high power as a full lock for battery stats */
+                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
     private boolean acquireWifiLockLocked(WifiLock wifiLock) {
         Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
 
         mLocks.addLock(wifiLock);
 
-        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
+            noteAcquireWifiLock(wifiLock);
             switch(wifiLock.mMode) {
             case WifiManager.WIFI_MODE_FULL:
                 ++mFullLocksAcquired;
-                mBatteryStats.noteFullWifiLockAcquired(uid);
                 break;
             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                 ++mFullHighPerfLocksAcquired;
-                /* Treat high power as a full lock for battery stats */
-                mBatteryStats.noteFullWifiLockAcquired(uid);
                 break;
-
             case WifiManager.WIFI_MODE_SCAN_ONLY:
                 ++mScanLocksAcquired;
-                mBatteryStats.noteScanWifiLockAcquired(uid);
                 break;
             }
         } catch (RemoteException e) {
@@ -2159,6 +2202,33 @@
         return true;
     }
 
+    public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLocks) {
+                int index = mLocks.findLockByBinder(lock);
+                if (index < 0) {
+                    throw new IllegalArgumentException("Wifi lock not active");
+                }
+                WifiLock wl = mLocks.mList.get(index);
+                noteReleaseWifiLock(wl);
+                wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
+                noteAcquireWifiLock(wl);
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     public boolean releaseWifiLock(IBinder lock) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         synchronized (mLocks) {
@@ -2176,21 +2246,18 @@
         hadLock = (wifiLock != null);
 
         if (hadLock) {
-            int uid = Binder.getCallingUid();
             long ident = Binder.clearCallingIdentity();
             try {
+                noteAcquireWifiLock(wifiLock);
                 switch(wifiLock.mMode) {
                     case WifiManager.WIFI_MODE_FULL:
                         ++mFullLocksReleased;
-                        mBatteryStats.noteFullWifiLockReleased(uid);
                         break;
                     case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                         ++mFullHighPerfLocksReleased;
-                        mBatteryStats.noteFullWifiLockReleased(uid);
                         break;
                     case WifiManager.WIFI_MODE_SCAN_ONLY:
                         ++mScanLocksReleased;
-                        mBatteryStats.noteScanWifiLockReleased(uid);
                         break;
                 }
             } catch (RemoteException e) {
@@ -2208,12 +2275,14 @@
         String mTag;
         int mMode;
         IBinder mBinder;
+        WorkSource mWorkSource;
 
-        DeathRecipient(int mode, String tag, IBinder binder) {
+        DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
             super();
             mTag = tag;
             mMode = mode;
             mBinder = binder;
+            mWorkSource = ws;
             try {
                 mBinder.linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -2228,7 +2297,7 @@
 
     private class Multicaster extends DeathRecipient {
         Multicaster(String tag, IBinder binder) {
-            super(Binder.getCallingUid(), tag, binder);
+            super(Binder.getCallingUid(), tag, binder, null);
         }
 
         public void binderDied() {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2cdf31e..cf767ca 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5985,12 +5985,18 @@
                             finisher = new IIntentReceiver.Stub() {
                                 public void performReceive(Intent intent, int resultCode,
                                         String data, Bundle extras, boolean ordered,
-                                        boolean sticky)
-                                        throws RemoteException {
-                                    synchronized (ActivityManagerService.this) {
-                                        mDidUpdate = true;
-                                    }
-                                    systemReady(goingCallback);
+                                        boolean sticky) {
+                                    // The raw IIntentReceiver interface is called
+                                    // with the AM lock held, so redispatch to
+                                    // execute our code without the lock.
+                                    mHandler.post(new Runnable() {
+                                        public void run() {
+                                            synchronized (ActivityManagerService.this) {
+                                                mDidUpdate = true;
+                                            }
+                                            systemReady(goingCallback);
+                                        }
+                                    });
                                 }
                             };
                         }
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 7314e04..bb40967 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Process;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.telephony.SignalStrength;
 import android.util.Slog;
 
@@ -107,6 +108,20 @@
         }
     }
 
+    public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
+    public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
     public void noteStartSensor(int uid, int sensor) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -317,6 +332,48 @@
         }
     }
 
+    public void noteFullWifiLockAcquiredFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockAcquiredFromSourceLocked(ws);
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockReleasedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteScanWifiLockAcquiredFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScanWifiLockAcquiredFromSourceLocked(ws);
+        }
+    }
+
+    public void noteScanWifiLockReleasedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScanWifiLockReleasedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastEnabledFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastDisabledFromSourceLocked(ws);
+        }
+    }
+
     public boolean isOnBattery() {
         return mStats.isOnBattery();
     }
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index c1165c7..3bf6ee4 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -44,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -736,7 +737,7 @@
         startNavigating(true);
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
         if (DEBUG) Log.d(TAG, "setMinTime " + minTime);
         
         if (minTime >= 0) {
@@ -779,7 +780,7 @@
     public void addListener(int uid) {
         synchronized (mWakeLock) {
             mPendingListenerMessages++;
-           mWakeLock.acquire();
+            mWakeLock.acquire();
             Message m = Message.obtain(mHandler, ADD_LISTENER);
             m.arg1 = uid;
             mHandler.sendMessage(m);
diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java
index 084ab81..858a582 100644
--- a/services/java/com/android/server/location/LocationProviderInterface.java
+++ b/services/java/com/android/server/location/LocationProviderInterface.java
@@ -20,6 +20,7 @@
 import android.location.Location;
 import android.net.NetworkInfo;
 import android.os.Bundle;
+import android.os.WorkSource;
 
 /**
  * Location Manager's interface for location providers.
@@ -47,7 +48,7 @@
     /* returns false if single shot is not supported */
     boolean requestSingleShotFix();
     String getInternalState();
-    void setMinTime(long minTime);
+    void setMinTime(long minTime, WorkSource ws);
     void updateNetworkState(int state, NetworkInfo info);
     void updateLocation(Location location);
     boolean sendExtraCommand(String command, Bundle extras);
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 24d7737..7dc9920 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.util.Log;
 
 import com.android.internal.location.DummyLocationProvider;
@@ -52,6 +53,7 @@
     private boolean mLocationTracking = false;
     private boolean mEnabled = false;
     private long mMinTime = -1;
+    private WorkSource mMinTimeSource = new WorkSource();
     private int mNetworkState;
     private NetworkInfo mNetworkInfo;
 
@@ -122,7 +124,7 @@
                     provider.enableLocationTracking(true);
                 }
                 if (mMinTime >= 0) {
-                    provider.setMinTime(mMinTime);
+                    provider.setMinTime(mMinTime, mMinTimeSource);
                 }
                 if (mNetworkInfo != null) {
                     provider.updateNetworkState(mNetworkState, mNetworkInfo);
@@ -318,6 +320,7 @@
         mLocationTracking = enable;
         if (!enable) {
             mMinTime = -1;
+            mMinTimeSource.clear();
         }
         ILocationProvider provider;
         synchronized (mServiceConnection) {
@@ -339,15 +342,16 @@
         return mMinTime;
     }
 
-    public void setMinTime(long minTime) {
-       mMinTime = minTime;
+    public void setMinTime(long minTime, WorkSource ws) {
+        mMinTime = minTime;
+        mMinTimeSource.set(ws);
         ILocationProvider provider;
         synchronized (mServiceConnection) {
             provider = mProvider;
         }
         if (provider != null) {
             try {
-                provider.setMinTime(minTime);
+                provider.setMinTime(minTime, ws);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java
index 01b34b7..09d799f 100644
--- a/services/java/com/android/server/location/MockProvider.java
+++ b/services/java/com/android/server/location/MockProvider.java
@@ -23,6 +23,7 @@
 import android.net.NetworkInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 
@@ -201,7 +202,7 @@
         return false;
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
     }
 
     public void updateNetworkState(int state, NetworkInfo info) {
diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
index 7fc93f8..ea0d1b0 100644
--- a/services/java/com/android/server/location/PassiveProvider.java
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -24,6 +24,7 @@
 import android.net.NetworkInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 
 /**
@@ -123,7 +124,7 @@
         return false;
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
     }
 
     public void updateNetworkState(int state, NetworkInfo info) {
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 6e0bc9d0..0ee559e 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -21,6 +21,8 @@
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
 
+import android.os.WorkSource;
+
 /**
  * Interface that allows controlling and querying Wi-Fi connectivity.
  *
@@ -66,7 +68,9 @@
 
     DhcpInfo getDhcpInfo();
 
-    boolean acquireWifiLock(IBinder lock, int lockType, String tag);
+    boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws);
+
+    void updateWifiLockWorkSource(IBinder lock, in WorkSource ws);
 
     boolean releaseWifiLock(IBinder lock);
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 9d21521..dd162f2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 
 import java.util.List;
 
@@ -872,6 +873,7 @@
         int mLockType;
         private boolean mRefCounted;
         private boolean mHeld;
+        private WorkSource mWorkSource;
 
         private WifiLock(int lockType, String tag) {
             mTag = tag;
@@ -897,7 +899,7 @@
             synchronized (mBinder) {
                 if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) {
                     try {
-                        mService.acquireWifiLock(mBinder, mLockType, mTag);
+                        mService.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource);
                         synchronized (WifiManager.this) {
                             if (mActiveLockCount >= MAX_ACTIVE_LOCKS) {
                                 mService.releaseWifiLock(mBinder);
@@ -969,6 +971,32 @@
             }
         }
 
+        public void setWorkSource(WorkSource ws) {
+            synchronized (mBinder) {
+                if (ws != null && ws.size() == 0) {
+                    ws = null;
+                }
+                boolean changed = true;
+                if (ws == null) {
+                    mWorkSource = null;
+                } else if (mWorkSource == null) {
+                    changed = mWorkSource != null;
+                    mWorkSource = new WorkSource(ws);
+                } else {
+                    changed = mWorkSource.diff(ws);
+                    if (changed) {
+                        mWorkSource.set(ws);
+                    }
+                }
+                if (changed && mHeld) {
+                    try {
+                        mService.updateWifiLockWorkSource(mBinder, mWorkSource);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+
         public String toString() {
             String s1, s2, s3;
             synchronized (mBinder) {