Track CPU speed stepping to get more accurate CPU cost per app.

More CPU speed stepping happening with newer devices, so we need
to qualify CPU time with the CPU speed, since power consumption
varies greatly by speed. Apps that peg the CPU should get a higher
penaltly.

Also, fix for 2062930: NPE at VolumePreference.onKey()
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e203fd5..a49a27a0 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -314,6 +314,15 @@
              * @return foreground cpu time in microseconds
              */
             public abstract long getForegroundTime(int which);
+
+            /**
+             * Returns the approximate cpu time spent in microseconds, at a certain CPU speed.
+             * @param speedStep the index of the CPU speed. This is not the actual speed of the
+             * CPU.
+             * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED
+             * @see BatteryStats#getCpuSpeedSteps()
+             */
+            public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
         }
 
         /**
@@ -573,6 +582,9 @@
     
     public abstract Map<String, ? extends Timer> getKernelWakelockStats();
 
+    /** Returns the number of different speeds that the CPU can run at */
+    public abstract int getCpuSpeedSteps();
+
     private final static void formatTimeRaw(StringBuilder out, long seconds) {
         long days = seconds / (60 * 60 * 24);
         if (days != 0) {
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index b337d28..a264594 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -121,6 +121,9 @@
        if (mSeekBarVolumizer != null) {
            Dialog dialog = getDialog();
            if (dialog != null && dialog.isShowing()) {
+               View view = dialog.getWindow().getDecorView()
+                       .findViewById(com.android.internal.R.id.seekbar);
+               if (view != null) view.setOnKeyListener(null);
                // Stopped while dialog was showing, revert changes
                mSeekBarVolumizer.revertVolume();
            }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2da72df..35c66ba 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -56,7 +56,9 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
 
     // Current on-disk Parcel version
-    private static final int VERSION = 39;
+    private static final int VERSION = 40;
+
+    private static int sNumSpeedSteps;
 
     private final File mFile;
     private final File mBackupFile;
@@ -213,7 +215,7 @@
     /**
      * State for keeping track of counting information.
      */
-    public static final class Counter extends BatteryStats.Counter implements Unpluggable {
+    public static class Counter extends BatteryStats.Counter implements Unpluggable {
         int mCount;
         int mLoadedCount;
         int mLastCount;
@@ -302,7 +304,22 @@
             mUnpluggedCount = mPluggedCount = mCount;
         }
     }
-    
+
+    public static class SamplingCounter extends Counter {
+
+        SamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) {
+            super(unpluggables, in);
+        }
+
+        SamplingCounter(ArrayList<Unpluggable> unpluggables) {
+            super(unpluggables);
+        }
+
+        public void addCountLocked(long count) {
+            mCount += count;
+        }
+    }
+
     /**
      * State for keeping track of timing information.
      */
@@ -1940,10 +1957,16 @@
              */
             long mUnpluggedForegroundTime;
 
+            SamplingCounter[] mSpeedBins;
+
             Proc() {
                 mUnpluggables.add(this);
+                mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
+                for (int i = 0; i < mSpeedBins.length; i++) {
+                    mSpeedBins[i] = new SamplingCounter(mUnpluggables);
+                }
             }
-            
+
             public void unplug(long batteryUptime, long batteryRealtime) {
                 mUnpluggedUserTime = mUserTime;
                 mUnpluggedSystemTime = mSystemTime;
@@ -1974,6 +1997,11 @@
                 out.writeLong(mUnpluggedSystemTime);
                 out.writeLong(mUnpluggedForegroundTime);
                 out.writeInt(mUnpluggedStarts);
+
+                out.writeInt(mSpeedBins.length);
+                for (int i = 0; i < mSpeedBins.length; i++) {
+                    mSpeedBins[i].writeToParcel(out);
+                }
             }
 
             void readFromParcelLocked(Parcel in) {
@@ -1993,6 +2021,12 @@
                 mUnpluggedSystemTime = in.readLong();
                 mUnpluggedForegroundTime = in.readLong();
                 mUnpluggedStarts = in.readInt();
+
+                int bins = in.readInt();
+                mSpeedBins = new SamplingCounter[bins];
+                for (int i = 0; i < bins; i++) {
+                    mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
+                }
             }
 
             public BatteryStatsImpl getBatteryStats() {
@@ -2075,6 +2109,22 @@
                 }
                 return val;
             }
+
+            /* Called by ActivityManagerService when CPU times are updated. */
+            public void addSpeedStepTimes(long[] values) {
+                for (int i = 0; i < mSpeedBins.length && i < values.length; i++) {
+                    mSpeedBins[i].addCountLocked(values[i]);
+                }
+            }
+
+            @Override
+            public long getTimeAtCpuSpeedStep(int speedStep, int which) {
+                if (speedStep < mSpeedBins.length) {
+                    return mSpeedBins[speedStep].getCountLocked(which);
+                } else {
+                    return 0;
+                }
+            }
         }
 
         /**
@@ -2625,6 +2675,10 @@
         readFromParcel(p);
     }
 
+    public void setNumSpeedSteps(int steps) {
+        if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
+    }
+
     @Override
     public int getStartCount() {
         return mStartCount;
@@ -2853,6 +2907,11 @@
             return mDischargeCurrentLevel;
     }
 
+    @Override
+    public int getCpuSpeedSteps() {
+        return sNumSpeedSteps;
+    }
+
     /**
      * Retrieve the statistics object for a particular uid, creating if needed.
      */
@@ -3063,7 +3122,9 @@
                 getKernelWakelockTimerLocked(kwltName).readSummaryFromParcelLocked(in);
             }
         }
-        
+
+        sNumSpeedSteps = in.readInt();
+
         final int NU = in.readInt();
         for (int iu = 0; iu < NU; iu++) {
             int uid = in.readInt();
@@ -3206,6 +3267,7 @@
             }
         }
         
+        out.writeInt(sNumSpeedSteps);
         final int NU = mUidStats.size();
         out.writeInt(NU);
         for (int iu = 0; iu < NU; iu++) {
@@ -3404,6 +3466,8 @@
         mFullTimers.clear();
         mWindowTimers.clear();
 
+        sNumSpeedSteps = in.readInt();
+
         int numUids = in.readInt();
         mUidStats.clear();
         for (int i = 0; i < numUids; i++) {
@@ -3484,7 +3548,9 @@
                 out.writeInt(0);
             }
         }
-        
+
+        out.writeInt(sNumSpeedSteps);
+
         int size = mUidStats.size();
         out.writeInt(size);
         for (int i = 0; i < size; i++) {
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 94f703a..4b4b717 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -47,14 +47,9 @@
     public static final String POWER_CPU_IDLE = "cpu.idle";
 
     /**
-     * Power consumption when CPU is running at normal speed.
+     * Power consumption when CPU is in power collapse mode.
      */
-    public static final String POWER_CPU_NORMAL = "cpu.normal";
-
-    /**
-     * Power consumption when CPU is running at full speed.
-     */
-    public static final String POWER_CPU_FULL = "cpu.full";
+    public static final String POWER_CPU_ACTIVE = "cpu.active";
 
     /**
      * Power consumption when WiFi driver is scanning for networks.
@@ -124,6 +119,8 @@
      */
     public static final String POWER_VIDEO = "dsp.video";
 
+    public static final String POWER_CPU_SPEEDS = "cpu.speeds";
+
     static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
 
     private static final String TAG_DEVICE = "device";
@@ -214,10 +211,10 @@
     }
     
     /**
-     * Returns the average current in mA consumed by the subsystem for the given level. 
+     * Returns the average current in mA consumed by the subsystem for the given level.
      * @param type the subsystem type
      * @param level the level of power at which the subsystem is running. For instance, the
-     *  signal strength of the cell network between 0 and 4 (if there are 4 bars max.).
+     *  signal strength of the cell network between 0 and 4 (if there are 4 bars max.)
      *  If there is no data for multiple levels, the level is ignored.
      * @return the average current in milliAmps.
      */
@@ -240,4 +237,12 @@
             return 0;
         }
     }
+
+    public int getNumSpeedSteps() {
+        Object value = sPowerMap.get(POWER_CPU_SPEEDS);
+        if (value != null && value instanceof Double[]) {
+            return ((Double[])value).length;
+        }
+        return 1; // Only one speed
+    }
 }
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 859902e..710b71e 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -26,15 +26,24 @@
   <item name="wifi.on">0.1</item>
   <item name="wifi.active">0.1</item>
   <item name="wifi.scan">0.1</item>
-  <item name="cpu.idle">0.1</item>
-  <item name="cpu.normal">0.2</item>
-  <item name="cpu.full">1</item>
   <item name="dsp.audio">0.1</item>
   <item name="dsp.video">0.1</item>
   <item name="radio.active">1</item>
   <item name="gps.on">1</item>
+  <!-- Current consumed by the radio at different signal strengths, when paging -->
   <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
       <value>1</value>
       <value>0.1</value>
   </array>
+  <!-- Different CPU speeds as reported in
+       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state -->
+  <array name="cpu.speeds">
+      <value>400000</value> <!-- 400 MHz CPU speed -->
+  </array>
+  <!-- Power consumption when CPU is idle -->
+  <item name="cpu.idle">0.1</item>
+  <!-- Power consumption at different speeds -->
+  <array name="cpu.active">
+      <value>0.2</value>
+  </array>
 </device>
diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java
index af80e20..eaab3eb 100644
--- a/services/java/com/android/server/ProcessStats.java
+++ b/services/java/com/android/server/ProcessStats.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.StringTokenizer;
 
 public class ProcessStats {
     private static final String TAG = "ProcessStats";
@@ -138,7 +139,22 @@
     private boolean mFirst = true;
 
     private byte[] mBuffer = new byte[256];
-     
+
+    /**
+     * The time in microseconds that the CPU has been running at each speed.
+     */
+    private long[] mCpuSpeedTimes;
+
+    /**
+     * The relative time in microseconds that the CPU has been running at each speed.
+     */
+    private long[] mRelCpuSpeedTimes;
+
+    /**
+     * The different speeds that the CPU can be running at.
+     */
+    private long[] mCpuSpeeds;
+
     public static class Stats {
         public final int pid;
         final String statFile;
@@ -460,6 +476,67 @@
         return 0;
     }
 
+    /**
+     * Returns the times spent at each CPU speed, since the last call to this method. If this
+     * is the first time, it will return 1 for each value.
+     * @return relative times spent at different speed steps.
+     */
+    public long[] getLastCpuSpeedTimes() {
+        if (mCpuSpeedTimes == null) {
+            mCpuSpeedTimes = getCpuSpeedTimes(null);
+            mRelCpuSpeedTimes = new long[mCpuSpeedTimes.length];
+            for (int i = 0; i < mCpuSpeedTimes.length; i++) {
+                mRelCpuSpeedTimes[i] = 1; // Initialize
+            }
+        } else {
+            getCpuSpeedTimes(mRelCpuSpeedTimes);
+            for (int i = 0; i < mCpuSpeedTimes.length; i++) {
+                long temp = mRelCpuSpeedTimes[i];
+                mRelCpuSpeedTimes[i] -= mCpuSpeedTimes[i];
+                mCpuSpeedTimes[i] = temp;
+            }
+        }
+        return mRelCpuSpeedTimes;
+    }
+
+    private long[] getCpuSpeedTimes(long[] out) {
+        long[] tempTimes = out;
+        long[] tempSpeeds = mCpuSpeeds;
+        final int MAX_SPEEDS = 20;
+        if (out == null) {
+            tempTimes = new long[MAX_SPEEDS]; // Hopefully no more than that
+            tempSpeeds = new long[MAX_SPEEDS];
+        }
+        int speed = 0;
+        String file = readFile("/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state", '\0');
+        StringTokenizer st = new StringTokenizer(file, "\n ");
+        while (st.hasMoreElements()) {
+            String token = st.nextToken();
+            try {
+                long val = Long.parseLong(token);
+                tempSpeeds[speed] = val;
+                token = st.nextToken();
+                val = Long.parseLong(token);
+                tempTimes[speed] = val;
+                speed++;
+                if (speed == MAX_SPEEDS) break; // No more
+                if (localLOGV && out == null) {
+                    Log.v(TAG, "First time : Speed/Time = " + tempSpeeds[speed - 1]
+                            + "\t" + tempTimes[speed - 1]);
+                }
+            } catch (NumberFormatException nfe) {
+                Log.i(TAG, "Unable to parse time_in_state");
+            }
+        }
+        if (out == null) {
+            out = new long[speed];
+            mCpuSpeeds = new long[speed];
+            System.arraycopy(tempSpeeds, 0, mCpuSpeeds, 0, speed);
+            System.arraycopy(tempTimes, 0, out, 0, speed);
+        }
+        return out;
+    }
+
     final public int getLastUserTime() {
         return mRelUserTime;
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 4db5239..d4f7207e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1572,6 +1572,7 @@
                 }
             }
             
+            long[] cpuSpeedTimes = mProcessStats.getLastCpuSpeedTimes();
             final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics();
             synchronized(bstats) {
                 synchronized(mPidsSelfLocked) {
@@ -1585,11 +1586,13 @@
                                 if (pr != null) {
                                     BatteryStatsImpl.Uid.Proc ps = pr.batteryStats;
                                     ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+                                    ps.addSpeedStepTimes(cpuSpeedTimes);
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps =
                                             bstats.getProcessStatsLocked(st.name, st.pid);
                                     if (ps != null) {
                                         ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+                                        ps.addSpeedStepTimes(cpuSpeedTimes);
                                     }
                                 }
                             }
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index ed0d534..61537f5 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.PowerProfile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -49,6 +50,7 @@
     public void publish(Context context) {
         mContext = context;
         ServiceManager.addService("batteryinfo", asBinder());
+        mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
     }
     
     public void shutdown() {