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/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 a9e4971..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. -->