Adding per UID WiFi power distribution.

Change-Id: Ia3d97e0a1c3352127185c18626d8ba8221c9ab40
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 508fdee..3051926 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -269,6 +269,15 @@
         public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which);
 
         /**
+         * Returns the total time in microseconds associated with this Timer since the
+         * 'mark' was last set.
+         *
+         * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
+         * @return a time in microseconds
+         */
+        public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs);
+
+        /**
          * Temporary for debugging.
          */
         public abstract void logState(Printer pw, String prefix);
@@ -332,7 +341,17 @@
          * @return a Map from Strings to Uid.Pkg objects.
          */
         public abstract ArrayMap<String, ? extends Pkg> getPackageStats();
-        
+
+        /**
+         * Returns the time in milliseconds that this app kept the WiFi controller in the
+         * specified state <code>type</code>.
+         * @param type one of {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, or
+         *             {@link #CONTROLLER_TX_TIME}.
+         * @param which one of {@link #STATS_CURRENT}, {@link #STATS_SINCE_CHARGED}, or
+         *              {@link #STATS_SINCE_UNPLUGGED}.
+         */
+        public abstract long getWifiControllerActivity(int type, int which);
+
         /**
          * {@hide}
          */
@@ -1914,7 +1933,6 @@
     public static final int NETWORK_MOBILE_TX_DATA = 1;
     public static final int NETWORK_WIFI_RX_DATA = 2;
     public static final int NETWORK_WIFI_TX_DATA = 3;
-
     public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1;
 
     public abstract long getNetworkActivityBytes(int type, int which);
@@ -1923,10 +1941,25 @@
     public static final int CONTROLLER_IDLE_TIME = 0;
     public static final int CONTROLLER_RX_TIME = 1;
     public static final int CONTROLLER_TX_TIME = 2;
-    public static final int CONTROLLER_ENERGY = 3;
-    public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1;
+    public static final int CONTROLLER_POWER_DRAIN = 3;
+    public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1;
 
+    /**
+     * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
+     * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
+     * respective state.
+     * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in
+     * milli-ampere-milliseconds (mAms).
+     */
     public abstract long getBluetoothControllerActivity(int type, int which);
+
+    /**
+     * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
+     * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
+     * respective state.
+     * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in
+     * milli-ampere-milliseconds (mAms).
+     */
     public abstract long getWifiControllerActivity(int type, int which);
 
     /**
@@ -2618,7 +2651,7 @@
                         label = "???";
                 }
                 dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label,
-                        BatteryStatsHelper.makemAh(bs.value));
+                        BatteryStatsHelper.makemAh(bs.totalPowerMah));
             }
         }
 
@@ -3264,6 +3297,13 @@
 
         sb.setLength(0);
         sb.append(prefix);
+        sb.append("  WiFi Energy use: ").append(BatteryStatsHelper.makemAh(
+                getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which) / (double)(1000*60*60)));
+        sb.append(" mAh");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
                 sb.append("  Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
                 sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime));
                 sb.append(")");
@@ -3376,48 +3416,48 @@
                 final BatterySipper bs = sippers.get(i);
                 switch (bs.drainType) {
                     case IDLE:
-                        pw.print(prefix); pw.print("    Idle: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Idle: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case CELL:
-                        pw.print(prefix); pw.print("    Cell standby: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Cell standby: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case PHONE:
-                        pw.print(prefix); pw.print("    Phone calls: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Phone calls: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case WIFI:
-                        pw.print(prefix); pw.print("    Wifi: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Wifi: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case BLUETOOTH:
-                        pw.print(prefix); pw.print("    Bluetooth: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Bluetooth: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case SCREEN:
-                        pw.print(prefix); pw.print("    Screen: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Screen: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case FLASHLIGHT:
-                        pw.print(prefix); pw.print("    Flashlight: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Flashlight: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case APP:
                         pw.print(prefix); pw.print("    Uid ");
                         UserHandle.formatUid(pw, bs.uidObj.getUid());
-                        pw.print(": "); printmAh(pw, bs.value); pw.println();
+                        pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println();
                         break;
                     case USER:
                         pw.print(prefix); pw.print("    User "); pw.print(bs.userId);
-                        pw.print(": "); printmAh(pw, bs.value); pw.println();
+                        pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println();
                         break;
                     case UNACCOUNTED:
-                        pw.print(prefix); pw.print("    Unaccounted: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Unaccounted: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                     case OVERCOUNTED:
-                        pw.print(prefix); pw.print("    Over-counted: "); printmAh(pw, bs.value);
+                        pw.print(prefix); pw.print("    Over-counted: "); printmAh(pw, bs.totalPowerMah);
                         pw.println();
                         break;
                 }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index bea4ece..1746bed 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -109,6 +109,7 @@
     void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
     void noteWifiMulticastEnabledFromSource(in WorkSource ws);
     void noteWifiMulticastDisabledFromSource(in WorkSource ws);
+    void noteWifiRadioPowerState(int powerState, long timestampNs);
     void noteNetworkInterfaceType(String iface, int type);
     void noteNetworkStatsEnabled();
     void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion);
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 4cd959f..056b0aa 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -23,17 +23,25 @@
 public class BatterySipper implements Comparable<BatterySipper> {
     public int userId;
     public Uid uidObj;
-    public double value;
-    public double[] values;
+    public double totalPowerMah;
     public DrainType drainType;
 
-    // Measured in milliseconds.
-    public long usageTime;
-    public long cpuTime;
-    public long gpsTime;
-    public long wifiRunningTime;
-    public long cpuFgTime;
-    public long wakeLockTime;
+    /**
+     * Generic usage time in milliseconds.
+     */
+    public long usageTimeMs;
+
+    /**
+     * Generic power usage in mAh.
+     */
+    public double usagePowerMah;
+
+    // Subsystem usage times.
+    public long cpuTimeMs;
+    public long gpsTimeMs;
+    public long wifiRunningTimeMs;
+    public long cpuFgTimeMs;
+    public long wakeLockTimeMs;
 
     public long mobileRxPackets;
     public long mobileTxPackets;
@@ -52,12 +60,13 @@
     public String packageWithHighestDrain;
 
     // Measured in mAh (milli-ampere per hour).
-    public double wifiPower;
-    public double cpuPower;
-    public double wakeLockPower;
-    public double mobileRadioPower;
-    public double gpsPower;
-    public double sensorPower;
+    // These are included when summed.
+    public double wifiPowerMah;
+    public double cpuPowerMah;
+    public double wakeLockPowerMah;
+    public double mobileRadioPowerMah;
+    public double gpsPowerMah;
+    public double sensorPowerMah;
 
     public enum DrainType {
         IDLE,
@@ -73,17 +82,12 @@
         OVERCOUNTED
     }
 
-    public BatterySipper(DrainType drainType, Uid uid, double[] values) {
-        this.values = values;
-        if (values != null) value = values[0];
+    public BatterySipper(DrainType drainType, Uid uid, double value) {
+        this.totalPowerMah = value;
         this.drainType = drainType;
         uidObj = uid;
     }
 
-    public double[] getValues() {
-        return values;
-    }
-
     public void computeMobilemspp() {
         long packets = mobileRxPackets+mobileTxPackets;
         mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0;
@@ -101,7 +105,7 @@
             }
         }
         // Return the flipped value because we want the items in descending order
-        return Double.compare(other.value, value);
+        return Double.compare(other.totalPowerMah, totalPowerMah);
     }
 
     /**
@@ -123,11 +127,14 @@
      * Add stats from other to this BatterySipper.
      */
     public void add(BatterySipper other) {
-        cpuTime += other.cpuTime;
-        gpsTime += other.gpsTime;
-        wifiRunningTime += other.wifiRunningTime;
-        cpuFgTime += other.cpuFgTime;
-        wakeLockTime += other.wakeLockTime;
+        totalPowerMah += other.totalPowerMah;
+        usageTimeMs += other.usageTimeMs;
+        usagePowerMah += other.usagePowerMah;
+        cpuTimeMs += other.cpuTimeMs;
+        gpsTimeMs += other.gpsTimeMs;
+        wifiRunningTimeMs += other.wifiRunningTimeMs;
+        cpuFgTimeMs += other.cpuFgTimeMs;
+        wakeLockTimeMs += other.wakeLockTimeMs;
         mobileRxPackets += other.mobileRxPackets;
         mobileTxPackets += other.mobileTxPackets;
         mobileActive += other.mobileActive;
@@ -138,11 +145,20 @@
         mobileTxBytes += other.mobileTxBytes;
         wifiRxBytes += other.wifiRxBytes;
         wifiTxBytes += other.wifiTxBytes;
-        wifiPower += other.wifiPower;
-        gpsPower += other.gpsPower;
-        cpuPower += other.cpuPower;
-        sensorPower += other.sensorPower;
-        mobileRadioPower += other.mobileRadioPower;
-        wakeLockPower += other.wakeLockPower;
+        wifiPowerMah += other.wifiPowerMah;
+        gpsPowerMah += other.gpsPowerMah;
+        cpuPowerMah += other.cpuPowerMah;
+        sensorPowerMah += other.sensorPowerMah;
+        mobileRadioPowerMah += other.mobileRadioPowerMah;
+        wakeLockPowerMah += other.wakeLockPowerMah;
+    }
+
+    /**
+     * Sum all the powers and store the value into `value`.
+     * @return the sum of all the power in this BatterySipper.
+     */
+    public double sumPower() {
+        return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah
+                + mobileRadioPowerMah + wakeLockPowerMah;
     }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index d3611bf..024b7c5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -16,17 +16,12 @@
 
 package com.android.internal.os;
 
-import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA;
-import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA;
-import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA;
-import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.Uid;
 import android.os.Bundle;
@@ -38,7 +33,6 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.telephony.SignalStrength;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
@@ -54,7 +48,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * A helper class for retrieving the power usage information for all applications and services.
@@ -63,8 +56,7 @@
  * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
  */
 public final class BatteryStatsHelper {
-
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = false;
 
     private static final String TAG = BatteryStatsHelper.class.getSimpleName();
 
@@ -81,14 +73,24 @@
     private Intent mBatteryBroadcast;
     private PowerProfile mPowerProfile;
 
-    private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
-    private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
-    private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
-    private final SparseArray<List<BatterySipper>> mUserSippers
-            = new SparseArray<List<BatterySipper>>();
-    private final SparseArray<Double> mUserPower = new SparseArray<Double>();
+    /**
+     * List of apps using power.
+     */
+    private final List<BatterySipper> mUsageList = new ArrayList<>();
 
-    private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
+    /**
+     * List of apps using wifi power.
+     */
+    private final List<BatterySipper> mWifiSippers = new ArrayList<>();
+
+    /**
+     * List of apps using bluetooth power.
+     */
+    private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();
+
+    private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
+
+    private final List<BatterySipper> mMobilemsppList = new ArrayList<>();
 
     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
 
@@ -102,29 +104,50 @@
     long mChargeTimeRemaining;
 
     private long mStatsPeriod = 0;
+
+    // The largest entry by power.
     private double mMaxPower = 1;
+
+    // The largest real entry by power (not undercounted or overcounted).
     private double mMaxRealPower = 1;
+
+    // Total computed power.
     private double mComputedPower;
     private double mTotalPower;
-    private double mWifiPower;
-    private double mBluetoothPower;
     private double mMinDrainedPower;
     private double mMaxDrainedPower;
 
-    // How much the apps together have kept the mobile radio active.
-    private long mAppMobileActive;
+    PowerCalculator mCpuPowerCalculator;
+    PowerCalculator mWakelockPowerCalculator;
+    MobileRadioPowerCalculator mMobileRadioPowerCalculator;
+    PowerCalculator mWifiPowerCalculator;
+    PowerCalculator mBluetoothPowerCalculator;
+    PowerCalculator mSensorPowerCalculator;
 
-    // How much the apps together have left WIFI running.
-    private long mAppWifiRunning;
+    public static boolean checkWifiOnly(Context context) {
+        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+    }
+
+    public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) {
+        WifiManager manager = context.getSystemService(WifiManager.class);
+        if (manager.isEnhancedPowerReportingSupported()) {
+            if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
+                    profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
+                    profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
 
     public BatteryStatsHelper(Context context) {
         this(context, true);
     }
 
     public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
-        mContext = context;
-        mCollectBatteryBroadcast = collectBatteryBroadcast;
-        mWifiOnly = checkWifiOnly(context);
+        this(context, collectBatteryBroadcast, checkWifiOnly(context));
     }
 
     public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
@@ -133,12 +156,6 @@
         mWifiOnly = wifiOnly;
     }
 
-    public static boolean checkWifiOnly(Context context) {
-        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
-    }
-
     public void storeStatsHistoryInFile(String fname) {
         synchronized (sFileXfer) {
             File path = makeFilePath(mContext, fname);
@@ -260,7 +277,7 @@
      * Refreshes the power usage list.
      */
     public void refreshStats(int statsType, int asUser) {
-        SparseArray<UserHandle> users = new SparseArray<UserHandle>(1);
+        SparseArray<UserHandle> users = new SparseArray<>(1);
         users.put(asUser, new UserHandle(asUser));
         refreshStats(statsType, users);
     }
@@ -270,7 +287,7 @@
      */
     public void refreshStats(int statsType, List<UserHandle> asUsers) {
         final int n = asUsers.size();
-        SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);
+        SparseArray<UserHandle> users = new SparseArray<>(n);
         for (int i = 0; i < n; ++i) {
             UserHandle userHandle = asUsers.get(i);
             users.put(userHandle.getIdentifier(), userHandle);
@@ -295,22 +312,52 @@
         mMaxRealPower = 0;
         mComputedPower = 0;
         mTotalPower = 0;
-        mWifiPower = 0;
-        mBluetoothPower = 0;
-        mAppMobileActive = 0;
-        mAppWifiRunning = 0;
 
         mUsageList.clear();
         mWifiSippers.clear();
         mBluetoothSippers.clear();
         mUserSippers.clear();
-        mUserPower.clear();
         mMobilemsppList.clear();
 
         if (mStats == null) {
             return;
         }
 
+        if (mCpuPowerCalculator == null) {
+            mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
+        }
+        mCpuPowerCalculator.reset();
+
+        if (mWakelockPowerCalculator == null) {
+            mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
+        }
+        mWakelockPowerCalculator.reset();
+
+        if (mMobileRadioPowerCalculator == null) {
+            mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
+        }
+        mMobileRadioPowerCalculator.reset(mStats);
+
+        if (mWifiPowerCalculator == null) {
+            if (checkHasWifiPowerReporting(mContext, mPowerProfile)) {
+                mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
+            } else {
+                mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile);
+            }
+        }
+        mWifiPowerCalculator.reset();
+
+        if (mBluetoothPowerCalculator == null) {
+            mBluetoothPowerCalculator = new BluetoothPowerCalculator();
+        }
+        mBluetoothPowerCalculator.reset();
+
+        if (mSensorPowerCalculator == null) {
+            mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
+                    (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
+        }
+        mSensorPowerCalculator.reset();
+
         mStatsType = statsType;
         mRawUptime = rawUptimeUs;
         mRawRealtime = rawRealtimeUs;
@@ -358,383 +405,113 @@
         Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
             @Override
             public int compare(BatterySipper lhs, BatterySipper rhs) {
-                if (lhs.mobilemspp < rhs.mobilemspp) {
-                    return 1;
-                } else if (lhs.mobilemspp > rhs.mobilemspp) {
-                    return -1;
-                }
-                return 0;
+                return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
             }
         });
 
         processMiscUsage();
 
+        Collections.sort(mUsageList);
+
+        // At this point, we've sorted the list so we are guaranteed the max values are at the top.
+        // We have only added real powers so far.
+        if (!mUsageList.isEmpty()) {
+            mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
+            final int usageListCount = mUsageList.size();
+            for (int i = 0; i < usageListCount; i++) {
+                mComputedPower += mUsageList.get(i).totalPowerMah;
+            }
+        }
+
         if (DEBUG) {
             Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
                     + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
         }
+
         mTotalPower = mComputedPower;
         if (mStats.getLowDischargeAmountSinceCharge() > 1) {
             if (mMinDrainedPower > mComputedPower) {
                 double amount = mMinDrainedPower - mComputedPower;
                 mTotalPower = mMinDrainedPower;
-                addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
+                BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
+
+                // Insert the BatterySipper in its sorted position.
+                int index = Collections.binarySearch(mUsageList, bs);
+                if (index < 0) {
+                    index = -(index + 1);
+                }
+                mUsageList.add(index, bs);
+                mMaxPower = Math.max(mMaxPower, amount);
             } else if (mMaxDrainedPower < mComputedPower) {
                 double amount = mComputedPower - mMaxDrainedPower;
-                addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
+
+                // Insert the BatterySipper in its sorted position.
+                BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
+                int index = Collections.binarySearch(mUsageList, bs);
+                if (index < 0) {
+                    index = -(index + 1);
+                }
+                mUsageList.add(index, bs);
+                mMaxPower = Math.max(mMaxPower, amount);
             }
         }
-
-        Collections.sort(mUsageList);
     }
 
     private void processAppUsage(SparseArray<UserHandle> asUsers) {
         final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
-        final SensorManager sensorManager =
-                (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
-        final int which = mStatsType;
-        final int speedSteps = mPowerProfile.getNumSpeedSteps();
-        final double[] powerCpuNormal = new double[speedSteps];
-        final long[] cpuSpeedStepTimes = new long[speedSteps];
-        for (int p = 0; p < speedSteps; p++) {
-            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
-        }
-        final double mobilePowerPerPacket = getMobilePowerPerPacket();
-        final double mobilePowerPerMs = getMobilePowerPerMs();
-        final double wifiPowerPerPacket = getWifiPowerPerPacket();
-        long totalAppWakelockTimeUs = 0;
-        BatterySipper osApp = null;
         mStatsPeriod = mTypeBatteryRealtime;
 
-        final ArrayList<BatterySipper> appList = new ArrayList<>();
-
-        // Max values used to normalize later.
-        double maxWifiPower = 0;
-        double maxCpuPower = 0;
-        double maxWakeLockPower = 0;
-        double maxMobileRadioPower = 0;
-        double maxGpsPower = 0;
-        double maxSensorPower = 0;
-
         final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
         final int NU = uidStats.size();
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
-            final BatterySipper app = new BatterySipper(
-                    BatterySipper.DrainType.APP, u, new double[]{0});
+            final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
 
-            final Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
-            if (processStats.size() > 0) {
-                // Process CPU time.
+            mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+            mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+            mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+            mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+            mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+            mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
 
-                // Keep track of the package with highest drain.
-                double highestDrain = 0;
-
-                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
-                        : processStats.entrySet()) {
-                    Uid.Proc ps = ent.getValue();
-                    app.cpuFgTime += ps.getForegroundTime(which);
-                    final long totalCpuTime = ps.getUserTime(which) + ps.getSystemTime(which);
-                    app.cpuTime += totalCpuTime;
-
-                    // Calculate the total CPU time spent at the various speed steps.
-                    long totalTimeAtSpeeds = 0;
-                    for (int step = 0; step < speedSteps; step++) {
-                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
-                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
-                    }
-                    totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
-
-                    // Then compute the ratio of time spent at each speed and figure out
-                    // the total power consumption.
-                    double cpuPower = 0;
-                    for (int step = 0; step < speedSteps; step++) {
-                        final double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
-                        final double cpuSpeedStepPower =
-                                ratio * totalCpuTime * powerCpuNormal[step];
-                        if (DEBUG && ratio != 0) {
-                            Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
-                                    + step + " ratio=" + makemAh(ratio) + " power="
-                                    + makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
-                        }
-                        cpuPower += cpuSpeedStepPower;
-                    }
-
-                    if (DEBUG && cpuPower != 0) {
-                        Log.d(TAG, String.format("process %s, cpu power=%s",
-                                ent.getKey(), makemAh(cpuPower / (60 * 60 * 1000))));
-                    }
-                    app.cpuPower += cpuPower;
-
-                    // Each App can have multiple packages and with multiple running processes.
-                    // Keep track of the package who's process has the highest drain.
-                    if (app.packageWithHighestDrain == null ||
-                            app.packageWithHighestDrain.startsWith("*")) {
-                        highestDrain = cpuPower;
-                        app.packageWithHighestDrain = ent.getKey();
-                    } else if (highestDrain < cpuPower && !ent.getKey().startsWith("*")) {
-                        highestDrain = cpuPower;
-                        app.packageWithHighestDrain = ent.getKey();
-                    }
-                }
-            }
-
-            // Ensure that the CPU times make sense.
-            if (app.cpuFgTime > app.cpuTime) {
-                if (DEBUG && app.cpuFgTime > app.cpuTime + 10000) {
-                    Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
-                }
-
-                // Statistics may not have been gathered yet.
-                app.cpuTime = app.cpuFgTime;
-            }
-
-            // Convert the CPU power to mAh
-            app.cpuPower /= (60 * 60 * 1000);
-            maxCpuPower = Math.max(maxCpuPower, app.cpuPower);
-
-            // Process wake lock usage
-            final Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
-                    u.getWakelockStats();
-            long wakeLockTimeUs = 0;
-            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
-                    : wakelockStats.entrySet()) {
-                final Uid.Wakelock wakelock = wakelockEntry.getValue();
-
-                // Only care about partial wake locks since full wake locks
-                // are canceled when the user turns the screen off.
-                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
-                if (timer != null) {
-                    wakeLockTimeUs += timer.getTotalTimeLocked(mRawRealtime, which);
-                }
-            }
-            app.wakeLockTime = wakeLockTimeUs / 1000; // convert to millis
-            totalAppWakelockTimeUs += wakeLockTimeUs;
-
-            // Add cost of holding a wake lock.
-            app.wakeLockPower = (app.wakeLockTime *
-                    mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60 * 60 * 1000);
-            if (DEBUG && app.wakeLockPower != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": wake "
-                        + app.wakeLockTime + " power=" + makemAh(app.wakeLockPower));
-            }
-            maxWakeLockPower = Math.max(maxWakeLockPower, app.wakeLockPower);
-
-            // Add cost of mobile traffic.
-            final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
-            app.mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
-            app.mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
-            app.mobileActive = mobileActive / 1000;
-            app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
-            app.mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
-            app.mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
-
-            if (mobileActive > 0) {
-                // We are tracking when the radio is up, so can use the active time to
-                // determine power use.
-                mAppMobileActive += mobileActive;
-                app.mobileRadioPower = (mobilePowerPerMs * mobileActive) / 1000;
-            } else {
-                // We are not tracking when the radio is up, so must approximate power use
-                // based on the number of packets.
-                app.mobileRadioPower = (app.mobileRxPackets + app.mobileTxPackets)
-                        * mobilePowerPerPacket;
-            }
-            if (DEBUG && app.mobileRadioPower != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
-                        + (app.mobileRxPackets + app.mobileTxPackets)
-                        + " active time " + mobileActive
-                        + " power=" + makemAh(app.mobileRadioPower));
-            }
-            maxMobileRadioPower = Math.max(maxMobileRadioPower, app.mobileRadioPower);
-
-            // Add cost of wifi traffic
-            app.wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
-            app.wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
-            app.wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
-            app.wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
-
-            final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
-                    * wifiPowerPerPacket;
-            if (DEBUG && wifiPacketPower != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
-                        + (app.wifiRxPackets + app.wifiTxPackets)
-                        + " power=" + makemAh(wifiPacketPower));
-            }
-
-            // Add cost of keeping WIFI running.
-            app.wifiRunningTime = u.getWifiRunningTime(mRawRealtime, which) / 1000;
-            mAppWifiRunning += app.wifiRunningTime;
-
-            final double wifiLockPower = (app.wifiRunningTime
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60 * 60 * 1000);
-            if (DEBUG && wifiLockPower != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": wifi running "
-                        + app.wifiRunningTime + " power=" + makemAh(wifiLockPower));
-            }
-
-            // Add cost of WIFI scans
-            final long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
-            final double wifiScanPower = (wifiScanTimeMs
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN))
-                    /  (60 * 60 * 1000);
-            if (DEBUG && wifiScanPower != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
-                        + " power=" + makemAh(wifiScanPower));
-            }
-
-            // Add cost of WIFI batch scans.
-            double wifiBatchScanPower = 0;
-            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
-                final long batchScanTimeMs =
-                        u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
-                final double batchScanPower = ((batchScanTimeMs
-                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
-                ) / (60 * 60 * 1000);
-                if (DEBUG && batchScanPower != 0) {
-                    Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
-                            + " time=" + batchScanTimeMs + " power=" + makemAh(batchScanPower));
-                }
-                wifiBatchScanPower += batchScanPower;
-            }
-
-            // Add up all the WiFi costs.
-            app.wifiPower = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
-            maxWifiPower = Math.max(maxWifiPower, app.wifiPower);
-
-            // Process Sensor usage
-            final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
-            final int NSE = sensorStats.size();
-            for (int ise = 0; ise < NSE; ise++) {
-                final Uid.Sensor sensor = sensorStats.valueAt(ise);
-                final int sensorHandle = sensorStats.keyAt(ise);
-                final BatteryStats.Timer timer = sensor.getSensorTime();
-                final long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
-                double sensorPower = 0;
-                switch (sensorHandle) {
-                    case Uid.Sensor.GPS:
-                        app.gpsTime = sensorTime;
-                        app.gpsPower = (app.gpsTime
-                                * mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON))
-                                / (60 * 60 * 1000);
-                        sensorPower = app.gpsPower;
-                        maxGpsPower = Math.max(maxGpsPower, app.gpsPower);
-                        break;
-                    default:
-                        List<Sensor> sensorList = sensorManager.getSensorList(
-                                android.hardware.Sensor.TYPE_ALL);
-                        for (android.hardware.Sensor s : sensorList) {
-                            if (s.getHandle() == sensorHandle) {
-                                sensorPower = (sensorTime * s.getPower()) / (60 * 60 * 1000);
-                                app.sensorPower += sensorPower;
-                                break;
-                            }
-                        }
-                }
-                if (DEBUG && sensorPower != 0) {
-                    Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
-                            + " time=" + sensorTime + " power=" + makemAh(sensorPower));
-                }
-            }
-            maxSensorPower = Math.max(maxSensorPower, app.sensorPower);
-
-            final double totalUnnormalizedPower = app.cpuPower + app.wifiPower + app.wakeLockPower
-                    + app.mobileRadioPower + app.gpsPower + app.sensorPower;
-            if (DEBUG && totalUnnormalizedPower != 0) {
-                Log.d(TAG, String.format("UID %d: total power=%s",
-                        u.getUid(), makemAh(totalUnnormalizedPower)));
+            final double totalPower = app.sumPower();
+            if (DEBUG && totalPower != 0) {
+                Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
+                        makemAh(totalPower)));
             }
 
             // Add the app to the list if it is consuming power.
-            if (totalUnnormalizedPower != 0 || u.getUid() == 0) {
-                appList.add(app);
-            }
-        }
-
-        // Fetch real power consumption from hardware.
-        double actualTotalWifiPower = 0.0;
-        if (mStats.getWifiControllerActivity(BatteryStats.CONTROLLER_ENERGY, mStatsType) != 0) {
-            final double kDefaultVoltage = 3.36;
-            final long energy = mStats.getWifiControllerActivity(
-                    BatteryStats.CONTROLLER_ENERGY, mStatsType);
-            final double voltage = mPowerProfile.getAveragePowerOrDefault(
-                    PowerProfile.OPERATING_VOLTAGE_WIFI, kDefaultVoltage);
-            actualTotalWifiPower = energy / (voltage * 1000*60*60);
-        }
-
-        final int appCount = appList.size();
-        for (int i = 0; i < appCount; i++) {
-            // Normalize power where possible.
-            final BatterySipper app = appList.get(i);
-            if (actualTotalWifiPower != 0) {
-                app.wifiPower = (app.wifiPower / maxWifiPower) * actualTotalWifiPower;
-            }
-
-            // Assign the final power consumption here.
-            final double power = app.wifiPower + app.cpuPower + app.wakeLockPower
-                    + app.mobileRadioPower + app.gpsPower + app.sensorPower;
-            app.values[0] = app.value = power;
-
-            //
-            // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
-            //
-
-            final int uid = app.getUid();
-            final int userId = UserHandle.getUserId(uid);
-            if (uid == Process.WIFI_UID) {
-                mWifiSippers.add(app);
-                mWifiPower += power;
-            } else if (uid == Process.BLUETOOTH_UID) {
-                mBluetoothSippers.add(app);
-                mBluetoothPower += power;
-            } else if (!forAllUsers && asUsers.get(userId) == null
-                    && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
-                // We are told to just report this user's apps as one large entry.
-                List<BatterySipper> list = mUserSippers.get(userId);
-                if (list == null) {
-                    list = new ArrayList<>();
-                    mUserSippers.put(userId, list);
-                }
-                list.add(app);
-
-                Double userPower = mUserPower.get(userId);
-                if (userPower == null) {
-                    userPower = power;
+            if (totalPower != 0 || u.getUid() == 0) {
+                //
+                // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
+                //
+                final int uid = app.getUid();
+                final int userId = UserHandle.getUserId(uid);
+                if (uid == Process.WIFI_UID) {
+                    mWifiSippers.add(app);
+                } else if (uid == Process.BLUETOOTH_UID) {
+                    mBluetoothSippers.add(app);
+                } else if (!forAllUsers && asUsers.get(userId) == null
+                        && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+                    // We are told to just report this user's apps as one large entry.
+                    List<BatterySipper> list = mUserSippers.get(userId);
+                    if (list == null) {
+                        list = new ArrayList<>();
+                        mUserSippers.put(userId, list);
+                    }
+                    list.add(app);
                 } else {
-                    userPower += power;
+                    mUsageList.add(app);
                 }
-                mUserPower.put(userId, userPower);
-            } else {
-                mUsageList.add(app);
-                if (power > mMaxPower) mMaxPower = power;
-                if (power > mMaxRealPower) mMaxRealPower = power;
-                mComputedPower += power;
-            }
 
-            if (uid == 0) {
-                osApp = app;
-            }
-        }
-
-        // The device has probably been awake for longer than the screen on
-        // time and application wake lock time would account for.  Assign
-        // this remainder to the OS, if possible.
-        if (osApp != null) {
-            long wakeTimeMillis = mBatteryUptime / 1000;
-            wakeTimeMillis -= (totalAppWakelockTimeUs / 1000)
-                    + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
-            if (wakeTimeMillis > 0) {
-                double power = (wakeTimeMillis
-                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
-                        /  (60*60*1000);
-                if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
-                        + makemAh(power));
-                osApp.wakeLockTime += wakeTimeMillis;
-                osApp.value += power;
-                osApp.values[0] += power;
-                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
-                if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value;
-                mComputedPower += power;
+                if (uid == 0) {
+                    // The device has probably been awake for longer than the screen on
+                    // time and application wake lock time would account for.  Assign
+                    // this remainder to the OS, if possible.
+                    mWakelockPowerCalculator.calculateRemaining(app, mStats, mRawRealtime,
+                                                                mRawUptime, mStatsType);
+                    app.sumPower();
+                }
             }
         }
     }
@@ -744,7 +521,7 @@
         double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
                 * phoneOnTimeMs / (60*60*1000);
         if (phoneOnPower != 0) {
-            BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
+            addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
         }
     }
 
@@ -773,54 +550,19 @@
     }
 
     private void addRadioUsage() {
-        double power = 0;
-        final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
-        long signalTimeMs = 0;
-        long noCoverageTimeMs = 0;
-        for (int i = 0; i < BINS; i++) {
-            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType)
-                    / 1000;
-            double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
-                        / (60*60*1000);
-            if (DEBUG && p != 0) {
-                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
-                        + makemAh(p));
-            }
-            power += p;
-            signalTimeMs += strengthTimeMs;
-            if (i == 0) {
-                noCoverageTimeMs = strengthTimeMs;
-            }
-        }
-        long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType)
-                / 1000;
-        double p = (scanningTimeMs * mPowerProfile.getAveragePower(
-                        PowerProfile.POWER_RADIO_SCANNING))
-                        / (60*60*1000);
-        if (DEBUG && p != 0) {
-            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
-        }
-        power += p;
-        long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType);
-        long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000;
-        if (remainingActiveTime > 0) {
-            power += getMobilePowerPerMs() * remainingActiveTime;
-        }
-        if (power != 0) {
-            BatterySipper bs =
-                    addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
-            if (signalTimeMs != 0) {
-                bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
-            }
-            bs.mobileActive = remainingActiveTime;
-            bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType);
+        BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
+        mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtime, mRawUptime,
+                mStatsType);
+        radio.sumPower();
+        if (radio.totalPowerMah > 0) {
+            mUsageList.add(radio);
         }
     }
 
     private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
         for (int i=0; i<from.size(); i++) {
             BatterySipper wbs = from.get(i);
-            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
+            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs);
             bs.add(wbs);
         }
         bs.computeMobilemspp();
@@ -847,41 +589,12 @@
      * of WiFi to the WiFi subsystem.
      */
     private void addWiFiUsage() {
-        final long idleTimeMs = mStats.getWifiControllerActivity(
-                BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
-        final long txTimeMs = mStats.getWifiControllerActivity(
-                BatteryStats.CONTROLLER_TX_TIME, mStatsType);
-        final long rxTimeMs = mStats.getWifiControllerActivity(
-                BatteryStats.CONTROLLER_RX_TIME, mStatsType);
-        final long energy = mStats.getWifiControllerActivity(
-                BatteryStats.CONTROLLER_ENERGY, mStatsType);
-        final long totalTimeRunning = idleTimeMs + txTimeMs + rxTimeMs;
-
-        double powerDrain = 0;
-        if (energy == 0 && totalTimeRunning > 0) {
-            // Energy is not reported, which means we may have left over power drain not attributed
-            // to any app. Assign this power to the WiFi app.
-            // TODO(adamlesinski): This mimics the old behavior. However, mAppWifiRunningTime
-            // is the accumulation of the time each app kept the WiFi chip on. Multiple apps
-            // can do this at the same time, so these times do not add up to the total time
-            // the WiFi chip was on. Consider normalizing the time spent running and calculating
-            // power from that? Normalizing the times will assign a weight to each app which
-            // should better represent power usage.
-            powerDrain = ((totalTimeRunning - mAppWifiRunning)
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
-        }
-
-        if (DEBUG && powerDrain != 0) {
-            Log.d(TAG, "Wifi active: time=" + (txTimeMs + rxTimeMs)
-                    + " power=" + makemAh(powerDrain));
-        }
-
-        // TODO(adamlesinski): mWifiPower is already added as a BatterySipper...
-        // Are we double counting here?
-        final double power = mWifiPower + powerDrain;
-        if (power > 0) {
-            BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, totalTimeRunning, power);
+        BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
+        mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType);
+        bs.sumPower();
+        if (bs.totalPowerMah > 0 || !mWifiSippers.isEmpty()) {
             aggregateSippers(bs, mWifiSippers, "WIFI");
+            mUsageList.add(bs);
         }
     }
 
@@ -890,30 +603,10 @@
      * Bluetooth Category.
      */
     private void addBluetoothUsage() {
-        final double kDefaultVoltage = 3.36;
-        final long idleTimeMs = mStats.getBluetoothControllerActivity(
-                BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
-        final long txTimeMs = mStats.getBluetoothControllerActivity(
-                BatteryStats.CONTROLLER_TX_TIME, mStatsType);
-        final long rxTimeMs = mStats.getBluetoothControllerActivity(
-                BatteryStats.CONTROLLER_RX_TIME, mStatsType);
-        final long energy = mStats.getBluetoothControllerActivity(
-                BatteryStats.CONTROLLER_ENERGY, mStatsType);
-        final double voltage = mPowerProfile.getAveragePowerOrDefault(
-                PowerProfile.OPERATING_VOLTAGE_BLUETOOTH, kDefaultVoltage);
-
-        // energy is measured in mA * V * ms, and we are interested in mAh
-        final double powerDrain = energy / (voltage * 60*60*1000);
-
-        if (DEBUG && powerDrain != 0) {
-            Log.d(TAG, "Bluetooth active: time=" + (txTimeMs + rxTimeMs)
-                    + " power=" + makemAh(powerDrain));
-        }
-
-        final long totalTime = idleTimeMs + txTimeMs + rxTimeMs;
-        final double power = mBluetoothPower + powerDrain;
-        if (power > 0) {
-            BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, totalTime, power);
+        BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
+        mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime,
+                mStatsType);
+        if (bs.sumPower() > 0) {
             aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
         }
     }
@@ -928,55 +621,16 @@
     }
 
     private void addUserUsage() {
-        for (int i=0; i<mUserSippers.size(); i++) {
+        for (int i = 0; i < mUserSippers.size(); i++) {
             final int userId = mUserSippers.keyAt(i);
-            final List<BatterySipper> sippers = mUserSippers.valueAt(i);
-            Double userPower = mUserPower.get(userId);
-            double power = (userPower != null) ? userPower : 0.0;
-            BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
+            BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
             bs.userId = userId;
-            aggregateSippers(bs, sippers, "User");
+            aggregateSippers(bs, mUserSippers.valueAt(i), "User");
+            bs.sumPower();
+            mUsageList.add(bs);
         }
     }
 
-    /**
-     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
-     */
-    private double getMobilePowerPerPacket() {
-        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
-        final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
-                / 3600;
-
-        final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
-        final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
-        final long mobileData = mobileRx + mobileTx;
-
-        final long radioDataUptimeMs
-                = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000;
-        final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
-                ? (mobileData / (double)radioDataUptimeMs)
-                : (((double)MOBILE_BPS) / 8 / 2048);
-
-        return (MOBILE_POWER / mobilePps) / (60*60);
-    }
-
-    /**
-     * Return estimated power (in mAs) of keeping the radio up
-     */
-    private double getMobilePowerPerMs() {
-        return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000);
-    }
-
-    /**
-     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
-     */
-    private double getWifiPowerPerPacket() {
-        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
-        final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
-                / 3600;
-        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
-    }
-
     private void processMiscUsage() {
         addUserUsage();
         addPhoneUsage();
@@ -992,15 +646,10 @@
     }
 
     private BatterySipper addEntry(DrainType drainType, long time, double power) {
-        mComputedPower += power;
-        if (power > mMaxRealPower) mMaxRealPower = power;
-        return addEntryNoTotal(drainType, time, power);
-    }
-
-    private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
-        if (power > mMaxPower) mMaxPower = power;
-        BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
-        bs.usageTime = time;
+        BatterySipper bs = new BatterySipper(drainType, null, 0);
+        bs.usagePowerMah = power;
+        bs.usageTimeMs = time;
+        bs.sumPower();
         mUsageList.add(bs);
         return bs;
     }
@@ -1015,7 +664,7 @@
 
     public long getStatsPeriod() { return mStatsPeriod; }
 
-    public int getStatsType() { return mStatsType; };
+    public int getStatsType() { return mStatsType; }
 
     public double getMaxPower() { return mMaxPower; }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 05ed3ab..793d0d3 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -55,6 +55,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 import android.view.Display;
@@ -95,6 +96,7 @@
 public final class BatteryStatsImpl extends BatteryStats {
     private static final String TAG = "BatteryStatsImpl";
     private static final boolean DEBUG = false;
+    private static final boolean DEBUG_ENERGY = false;
     private static final boolean DEBUG_HISTORY = false;
     private static final boolean USE_OLD_HISTORY = false;   // for debugging.
 
@@ -182,22 +184,20 @@
     // elapsed time by the number of active timers to arrive at that timer's share of the time.
     // In order to do this, we must refresh each timer whenever the number of active timers
     // changes.
-    final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<StopwatchTimer>();
-    final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers
-            = new SparseArray<ArrayList<StopwatchTimer>>();
-    final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>();
-    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
-            new SparseArray<ArrayList<StopwatchTimer>>();
-    final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<StopwatchTimer>();
-    final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<StopwatchTimer>();
+    final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
 
     // Last partial timers we use for distributing CPU usage.
-    final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
+    final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
 
     // These are the objects that will want to do something when the device
     // is unplugged from power.
@@ -227,7 +227,7 @@
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
     final HistoryItem mHistoryReadTmp = new HistoryItem();
     final HistoryItem mHistoryAddTmp = new HistoryItem();
-    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap();
+    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
     String[] mReadHistoryStrings;
     int[] mReadHistoryUids;
     int mReadHistoryChars;
@@ -450,6 +450,8 @@
 
     private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
 
+    private PowerProfile mPowerProfile;
+
     /*
      * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
      */
@@ -928,6 +930,12 @@
         long mUnpluggedTime;
 
         /**
+         * The total time this timer has been running until the latest mark has been set.
+         * Subtract this from mTotalTime to get the time spent running since the mark was set.
+         */
+        long mTimeBeforeMark;
+
+        /**
          * Constructs from a parcel.
          * @param type
          * @param timeBase
@@ -945,6 +953,7 @@
             mLoadedTime = in.readLong();
             mLastTime = 0;
             mUnpluggedTime = in.readLong();
+            mTimeBeforeMark = in.readLong();
             timeBase.add(this);
             if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime);
         }
@@ -964,7 +973,7 @@
          * so can be completely dropped.
          */
         boolean reset(boolean detachIfReset) {
-            mTotalTime = mLoadedTime = mLastTime = 0;
+            mTotalTime = mLoadedTime = mLastTime = mTimeBeforeMark = 0;
             mCount = mLoadedCount = mLastCount = 0;
             if (detachIfReset) {
                 detach();
@@ -985,8 +994,10 @@
             out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
             out.writeLong(mLoadedTime);
             out.writeLong(mUnpluggedTime);
+            out.writeLong(mTimeBeforeMark);
         }
 
+        @Override
         public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) {
             if (DEBUG && mType < 0) {
                 Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime
@@ -1002,6 +1013,7 @@
             }
         }
 
+        @Override
         public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
             if (DEBUG && mType < 0) {
                 Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime
@@ -1055,6 +1067,13 @@
             return val;
         }
 
+        @Override
+        public long getTimeSinceMarkLocked(long elapsedRealtimeUs) {
+            long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+            return val - mTimeBeforeMark;
+        }
+
+        @Override
         public void logState(Printer pw, String prefix) {
             pw.println(prefix + "mCount=" + mCount
                     + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
@@ -1080,6 +1099,9 @@
             mCount = mLoadedCount = in.readInt();
             mLastCount = 0;
             mUnpluggedCount = mCount;
+
+            // When reading the summary, we set the mark to be the latest information.
+            mTimeBeforeMark = mTotalTime;
         }
     }
 
@@ -1475,21 +1497,6 @@
             return mNesting > 0;
         }
 
-        long checkpointRunningLocked(long elapsedRealtimeMs) {
-            if (mNesting > 0) {
-                // We are running...
-                final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
-                if (mTimerPool != null) {
-                    return refreshTimersLocked(batteryRealtime, mTimerPool, this);
-                }
-                final long heldTime = batteryRealtime - mUpdateTime;
-                mUpdateTime = batteryRealtime;
-                mTotalTime += heldTime;
-                return heldTime;
-            }
-            return 0;
-        }
-
         void stopRunningLocked(long elapsedRealtimeMs) {
             // Ignore attempt to stop a timer that isn't running
             if (mNesting == 0) {
@@ -1567,6 +1574,7 @@
             return mCount;
         }
 
+        @Override
         boolean reset(boolean detachIfReset) {
             boolean canDetach = mNesting <= 0;
             super.reset(canDetach && detachIfReset);
@@ -1577,6 +1585,7 @@
             return canDetach;
         }
 
+        @Override
         void detach() {
             super.detach();
             if (mTimerPool != null) {
@@ -1584,10 +1593,31 @@
             }
         }
 
+        @Override
         void readSummaryFromParcelLocked(Parcel in) {
             super.readSummaryFromParcelLocked(in);
             mNesting = 0;
         }
+
+        /**
+         * Set the mark so that we can query later for the total time the timer has
+         * accumulated since this point. The timer can be running or not.
+         *
+         * @param elapsedRealtimeMs the current elapsed realtime in milliseconds.
+         */
+        public void setMark(long elapsedRealtimeMs) {
+            final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+            if (mNesting > 0) {
+                // We are running.
+                if (mTimerPool != null) {
+                    refreshTimersLocked(batteryRealtime, mTimerPool, this);
+                } else {
+                    mTotalTime += batteryRealtime - mUpdateTime;
+                    mUpdateTime = batteryRealtime;
+                }
+            }
+            mTimeBeforeMark = mTotalTime;
+        }
     }
 
     public abstract class OverflowArrayMap<T> {
@@ -3890,7 +3920,6 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
-            scheduleSyncExternalStatsLocked();
         }
         mWifiFullLockNesting++;
         getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime);
@@ -3906,7 +3935,6 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
-            scheduleSyncExternalStatsLocked();
         }
         getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime);
     }
@@ -3964,7 +3992,6 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
-            scheduleSyncExternalStatsLocked();
         }
         mWifiMulticastNesting++;
         getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
@@ -3980,7 +4007,6 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
-            scheduleSyncExternalStatsLocked();
         }
         getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
     }
@@ -4088,7 +4114,8 @@
         // During device boot, qtaguid isn't enabled until after the inital
         // loading of battery stats. Now that they're enabled, take our initial
         // snapshot for future delta calculation.
-        updateMobileRadioStateLocked(SystemClock.elapsedRealtime());
+        final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+        updateMobileRadioStateLocked(elapsedRealtimeMs);
         updateWifiStateLocked(null);
     }
 
@@ -4369,6 +4396,18 @@
         LongSamplingCounter mMobileRadioActiveCount;
 
         /**
+         * The amount of time this uid has kept the WiFi controller in idle, tx, and rx mode.
+         */
+        LongSamplingCounter[] mWifiControllerTime =
+                new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+        /**
+         * The amount of time this uid has kept the Bluetooth controller in idle, tx, and rx mode.
+         */
+        LongSamplingCounter[] mBluetoothControllerTime =
+                new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+        /**
          * The CPU times we had at the last history details update.
          */
         long mLastStepUserTime;
@@ -4404,22 +4443,22 @@
         /**
          * The statistics we have collected for this uid's sensor activations.
          */
-        final SparseArray<Sensor> mSensorStats = new SparseArray<Sensor>();
+        final SparseArray<Sensor> mSensorStats = new SparseArray<>();
 
         /**
          * The statistics we have collected for this uid's processes.
          */
-        final ArrayMap<String, Proc> mProcessStats = new ArrayMap<String, Proc>();
+        final ArrayMap<String, Proc> mProcessStats = new ArrayMap<>();
 
         /**
          * The statistics we have collected for this uid's processes.
          */
-        final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<String, Pkg>();
+        final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<>();
 
         /**
          * The transient wake stats we have collected for this uid's pids.
          */
-        final SparseArray<Pid> mPids = new SparseArray<Pid>();
+        final SparseArray<Pid> mPids = new SparseArray<>();
 
         public Uid(int uid) {
             mUid = uid;
@@ -4580,6 +4619,13 @@
             }
         }
 
+        public void noteWifiControllerActivityLocked(int type, long timeMs) {
+            if (mWifiControllerTime[type] == null) {
+                mWifiControllerTime[type] = new LongSamplingCounter(mOnBatteryTimeBase);
+            }
+            mWifiControllerTime[type].addCountLocked(timeMs);
+        }
+
         public StopwatchTimer createAudioTurnedOnTimerLocked() {
             if (mAudioTurnedOnTimer == null) {
                 mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
@@ -4895,6 +4941,15 @@
                     ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0;
         }
 
+        @Override
+        public long getWifiControllerActivity(int type, int which) {
+            if (type >= 0 && type < NUM_CONTROLLER_ACTIVITY_TYPES &&
+                    mWifiControllerTime[type] != null) {
+                return mWifiControllerTime[type].getCountLocked(which);
+            }
+            return 0;
+        }
+
         void initNetworkActivityLocked() {
             mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
             mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -4978,6 +5033,16 @@
                 mMobileRadioActiveCount.reset(false);
             }
 
+            for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                if (mWifiControllerTime[i] != null) {
+                    mWifiControllerTime[i].reset(false);
+                }
+
+                if (mBluetoothControllerTime[i] != null) {
+                    mBluetoothControllerTime[i].reset(false);
+                }
+            }
+
             final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
             for (int iw=wakeStats.size()-1; iw>=0; iw--) {
                 Wakelock wl = wakeStats.valueAt(iw);
@@ -5100,6 +5165,16 @@
                         mNetworkPacketActivityCounters[i].detach();
                     }
                 }
+
+                for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                    if (mWifiControllerTime[i] != null) {
+                        mWifiControllerTime[i].detach();
+                    }
+
+                    if (mBluetoothControllerTime[i] != null) {
+                        mBluetoothControllerTime[i].detach();
+                    }
+                }
                 mPids.clear();
             }
 
@@ -5189,6 +5264,7 @@
             } else {
                 out.writeInt(0);
             }
+
             if (mAudioTurnedOnTimer != null) {
                 out.writeInt(1);
                 mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
@@ -5240,6 +5316,24 @@
             } else {
                 out.writeInt(0);
             }
+
+            for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                if (mWifiControllerTime[i] != null) {
+                    out.writeInt(1);
+                    mWifiControllerTime[i].writeToParcel(out);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+
+            for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                if (mBluetoothControllerTime[i] != null) {
+                    out.writeInt(1);
+                    mBluetoothControllerTime[i].writeToParcel(out);
+                } else {
+                    out.writeInt(0);
+                }
+            }
         }
 
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
@@ -5389,6 +5483,22 @@
                 mNetworkByteActivityCounters = null;
                 mNetworkPacketActivityCounters = null;
             }
+
+            for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                if (in.readInt() != 0) {
+                    mWifiControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+                } else {
+                    mWifiControllerTime[i] = null;
+                }
+            }
+
+            for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+                if (in.readInt() != 0) {
+                    mBluetoothControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+                } else {
+                    mBluetoothControllerTime[i] = null;
+                }
+            }
         }
 
         /**
@@ -6644,6 +6754,12 @@
         readFromParcel(p);
     }
 
+    public void setPowerProfile(PowerProfile profile) {
+        synchronized (this) {
+            mPowerProfile = profile;
+        }
+    }
+
     public void setCallback(BatteryCallback cb) {
         mCallback = cb;
     }
@@ -7367,9 +7483,12 @@
      * @param info The energy information from the WiFi controller.
      */
     public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) {
-        final NetworkStats delta;
+        final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+        NetworkStats delta = null;
         try {
-            delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats);
+            if (!ArrayUtils.isEmpty(mWifiIfaces)) {
+                delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats);
+            }
         } catch (IOException e) {
             Slog.wtf(TAG, "Failed to get wifi network stats", e);
             return;
@@ -7379,14 +7498,19 @@
             return;
         }
 
+        SparseLongArray rxPackets = new SparseLongArray();
+        SparseLongArray txPackets = new SparseLongArray();
+        long totalTxPackets = 0;
+        long totalRxPackets = 0;
         if (delta != null) {
             final int size = delta.size();
             for (int i = 0; i < size; i++) {
                 final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
 
-                if (DEBUG) {
+                if (DEBUG_ENERGY) {
                     Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
-                            + " tx=" + entry.txBytes);
+                            + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+                            + " txPackets=" + entry.txPackets);
                 }
 
                 if (entry.rxBytes == 0 || entry.txBytes == 0) {
@@ -7398,6 +7522,13 @@
                         entry.rxPackets);
                 u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
                         entry.txPackets);
+                rxPackets.put(u.getUid(), entry.rxPackets);
+                txPackets.put(u.getUid(), entry.txPackets);
+
+                // Sum the total number of packets so that the Rx Power and Tx Power can
+                // be evenly distributed amongst the apps.
+                totalRxPackets += entry.rxPackets;
+                totalTxPackets += entry.txPackets;
 
                 mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
                         entry.rxBytes);
@@ -7411,6 +7542,119 @@
         }
 
         if (info != null) {
+            // Measured in mAms
+            final long txTimeMs = info.getControllerTxTimeMillis();
+            final long rxTimeMs = info.getControllerRxTimeMillis();
+            final long idleTimeMs = info.getControllerIdleTimeMillis();
+            final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs;
+
+            long leftOverRxTimeMs = rxTimeMs;
+
+            if (DEBUG_ENERGY) {
+                Slog.d(TAG, "------ BEGIN WiFi power blaming ------");
+                Slog.d(TAG, "  Tx Time:    " + txTimeMs + " ms");
+                Slog.d(TAG, "  Rx Time:    " + rxTimeMs + " ms");
+                Slog.d(TAG, "  Idle Time:  " + idleTimeMs + " ms");
+                Slog.d(TAG, "  Total Time: " + totalTimeMs + " ms");
+            }
+
+            long totalWifiLockTimeMs = 0;
+            long totalScanTimeMs = 0;
+
+            // On the first pass, collect some totals so that we can normalize power
+            // calculations if we need to.
+            final int uidStatsSize = mUidStats.size();
+            for (int i = 0; i < uidStatsSize; i++) {
+                final Uid uid = mUidStats.valueAt(i);
+
+                // Sum the total scan power for all apps.
+                totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                        elapsedRealtimeMs * 1000) / 1000;
+
+                // Sum the total time holding wifi lock for all apps.
+                totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+                        elapsedRealtimeMs * 1000) / 1000;
+            }
+
+            if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) {
+                Slog.d(TAG, "  !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > "
+                        + rxTimeMs + " ms). Normalizing scan time.");
+            }
+
+            // Actually assign and distribute power usage to apps.
+            for (int i = 0; i < uidStatsSize; i++) {
+                final Uid uid = mUidStats.valueAt(i);
+
+                long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                        elapsedRealtimeMs * 1000) / 1000;
+                if (scanTimeSinceMarkMs > 0) {
+                    // Set the new mark so that next time we get new data since this point.
+                    uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
+
+                    if (totalScanTimeMs > rxTimeMs) {
+                        // Our total scan time is more than the reported Rx time.
+                        // This is possible because the cost of a scan is approximate.
+                        // Let's normalize the result so that we evenly blame each app
+                        // scanning.
+                        //
+                        // This means that we may have apps that received packets not be blamed
+                        // for this, but this is fine as scans are relatively more expensive.
+                        scanTimeSinceMarkMs = (rxTimeMs * scanTimeSinceMarkMs) / totalScanTimeMs;
+                    }
+
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "  ScanTime for UID " + uid.getUid() + ": "
+                                + scanTimeSinceMarkMs + " ms)");
+                    }
+                    uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, scanTimeSinceMarkMs);
+                    leftOverRxTimeMs -= scanTimeSinceMarkMs;
+                }
+
+                // Distribute evenly the power consumed while Idle to each app holding a WiFi
+                // lock.
+                final long wifiLockTimeSinceMarkMs = uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+                        elapsedRealtimeMs * 1000) / 1000;
+                if (wifiLockTimeSinceMarkMs > 0) {
+                    // Set the new mark so that next time we get new data since this point.
+                    uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
+
+                    final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
+                            / totalWifiLockTimeMs;
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "  IdleTime for UID " + uid.getUid() + ": "
+                                + myIdleTimeMs + " ms");
+                    }
+                    uid.noteWifiControllerActivityLocked(CONTROLLER_IDLE_TIME, myIdleTimeMs);
+                }
+            }
+
+            if (DEBUG_ENERGY) {
+                Slog.d(TAG, "  New RxPower: " + leftOverRxTimeMs + " ms");
+            }
+
+            // Distribute the Tx power appropriately between all apps that transmitted packets.
+            for (int i = 0; i < txPackets.size(); i++) {
+                final Uid uid = getUidStatsLocked(txPackets.keyAt(i));
+                final long myTxTimeMs = (txPackets.valueAt(i) * txTimeMs) / totalTxPackets;
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "  TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
+                }
+                uid.noteWifiControllerActivityLocked(CONTROLLER_TX_TIME, myTxTimeMs);
+            }
+
+            // Distribute the remaining Rx power appropriately between all apps that received
+            // packets.
+            for (int i = 0; i < rxPackets.size(); i++) {
+                final Uid uid = getUidStatsLocked(rxPackets.keyAt(i));
+                final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) / totalRxPackets;
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "  RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
+                }
+                uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, myRxTimeMs);
+            }
+
+            // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
+
             // Update WiFi controller stats.
             mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
                     info.getControllerRxTimeMillis());
@@ -7418,19 +7662,29 @@
                     info.getControllerTxTimeMillis());
             mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
                     info.getControllerIdleTimeMillis());
-            mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
-                    info.getControllerEnergyUsed());
+
+            final double powerDrainMaMs;
+            if (mPowerProfile.getAveragePower(
+                    PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) == 0) {
+                powerDrainMaMs = 0.0;
+            } else {
+                powerDrainMaMs = info.getControllerEnergyUsed()
+                        / mPowerProfile.getAveragePower(
+                        PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE);
+            }
+            mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked((long) powerDrainMaMs);
         }
     }
 
     /**
      * Distribute Cell radio energy info and network traffic to apps.
      */
-    public void updateMobileRadioStateLocked(long elapsedRealtimeMs) {
-        final NetworkStats delta;
-
+    public void updateMobileRadioStateLocked(final long elapsedRealtimeMs) {
+        NetworkStats delta = null;
         try {
-            delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats);
+            if (!ArrayUtils.isEmpty(mMobileIfaces)) {
+                delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats);
+            }
         } catch (IOException e) {
             Slog.wtf(TAG, "Failed to get mobile network stats", e);
             return;
@@ -7440,14 +7694,24 @@
             return;
         }
 
-        long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(elapsedRealtimeMs);
+        long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
+                elapsedRealtimeMs * 1000);
+        mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
         long totalPackets = delta.getTotalPackets();
 
         final int size = delta.size();
         for (int i = 0; i < size; i++) {
             final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
 
-            if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
+            if (entry.rxBytes == 0 || entry.txBytes == 0) {
+                continue;
+            }
+
+            if (DEBUG_ENERGY) {
+                Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
+                        + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+                        + " txPackets=" + entry.txPackets);
+            }
 
             final Uid u = getUidStatsLocked(mapUid(entry.uid));
             u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
@@ -7488,14 +7752,14 @@
      * @param info The energy information from the bluetooth controller.
      */
     public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
-        if (info != null && mOnBatteryInternal) {
+        if (info != null && mOnBatteryInternal && false) {
             mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
                     info.getControllerRxTimeMillis());
             mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
                     info.getControllerTxTimeMillis());
             mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
                     info.getControllerIdleTimeMillis());
-            mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+            mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked(
                     info.getControllerEnergyUsed());
         }
     }
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
new file mode 100644
index 0000000..3557209
--- /dev/null
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.util.Log;
+
+public class BluetoothPowerCalculator extends PowerCalculator {
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private static final String TAG = "BluetoothPowerCalculator";
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        // No per-app distribution yet.
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final long idleTimeMs = stats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_IDLE_TIME, statsType);
+        final long txTimeMs = stats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_TX_TIME, statsType);
+        final long rxTimeMs = stats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_RX_TIME, statsType);
+        final long powerMaMs = stats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_POWER_DRAIN, statsType);
+        final double powerMah = powerMaMs / (double)(1000*60*60);
+        final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
+
+        if (DEBUG && powerMah != 0) {
+            Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
+                    + " power=" + BatteryStatsHelper.makemAh(powerMah));
+        }
+
+        app.usagePowerMah = powerMah;
+        app.usageTimeMs = totalTimeMs;
+    }
+}
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
new file mode 100644
index 0000000..6c3f958
--- /dev/null
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class CpuPowerCalculator extends PowerCalculator {
+    private static final String TAG = "CpuPowerCalculator";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+
+    private final double[] mPowerCpuNormal;
+
+    /**
+     * Reusable array for calculations.
+     */
+    private final long[] mSpeedStepTimes;
+
+    public CpuPowerCalculator(PowerProfile profile) {
+        final int speedSteps = profile.getNumSpeedSteps();
+        mPowerCpuNormal = new double[speedSteps];
+        mSpeedStepTimes = new long[speedSteps];
+        for (int p = 0; p < speedSteps; p++) {
+            mPowerCpuNormal[p] = profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
+        }
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        final int speedSteps = mSpeedStepTimes.length;
+
+        // Keep track of the package with highest drain.
+        double highestDrain = 0;
+
+        final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+        final int processStatsCount = processStats.size();
+        for (int i = 0; i < processStatsCount; i++) {
+            final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
+            final String processName = processStats.keyAt(i);
+
+            app.cpuFgTimeMs += ps.getForegroundTime(statsType);
+            final long totalCpuTime = ps.getUserTime(statsType) + ps.getSystemTime(statsType);
+            app.cpuTimeMs += totalCpuTime;
+
+            // Calculate the total CPU time spent at the various speed steps.
+            long totalTimeAtSpeeds = 0;
+            for (int step = 0; step < speedSteps; step++) {
+                mSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, statsType);
+                totalTimeAtSpeeds += mSpeedStepTimes[step];
+            }
+            totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
+
+            // Then compute the ratio of time spent at each speed and figure out
+            // the total power consumption.
+            double cpuPower = 0;
+            for (int step = 0; step < speedSteps; step++) {
+                final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds;
+                final double cpuSpeedStepPower = ratio * totalCpuTime * mPowerCpuNormal[step];
+                if (DEBUG && ratio != 0) {
+                    Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+                            + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
+                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+                }
+                cpuPower += cpuSpeedStepPower;
+            }
+
+            if (DEBUG && cpuPower != 0) {
+                Log.d(TAG, String.format("process %s, cpu power=%s",
+                        processName, BatteryStatsHelper.makemAh(cpuPower / (60 * 60 * 1000))));
+            }
+            app.cpuPowerMah += cpuPower;
+
+            // Each App can have multiple packages and with multiple running processes.
+            // Keep track of the package who's process has the highest drain.
+            if (app.packageWithHighestDrain == null ||
+                    app.packageWithHighestDrain.startsWith("*")) {
+                highestDrain = cpuPower;
+                app.packageWithHighestDrain = processName;
+            } else if (highestDrain < cpuPower && !processName.startsWith("*")) {
+                highestDrain = cpuPower;
+                app.packageWithHighestDrain = processName;
+            }
+        }
+
+        // Ensure that the CPU times make sense.
+        if (app.cpuFgTimeMs > app.cpuTimeMs) {
+            if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) {
+                Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
+            }
+
+            // Statistics may not have been gathered yet.
+            app.cpuTimeMs = app.cpuFgTimeMs;
+        }
+
+        // Convert the CPU power to mAh
+        app.cpuPowerMah /= (60 * 60 * 1000);
+    }
+}
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
new file mode 100644
index 0000000..9711c3b
--- /dev/null
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+public class MobileRadioPowerCalculator extends PowerCalculator {
+    private static final String TAG = "MobileRadioPowerController";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final double mPowerRadioOn;
+    private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+    private final double mPowerScan;
+    private BatteryStats mStats;
+    private long mTotalAppMobileActiveMs = 0;
+
+    /**
+     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
+     */
+    private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
+        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
+        final double MOBILE_POWER = mPowerRadioOn / 3600;
+
+        final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+        final long mobileData = mobileRx + mobileTx;
+
+        final long radioDataUptimeMs =
+                mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+        final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
+                ? (mobileData / (double)radioDataUptimeMs)
+                : (((double)MOBILE_BPS) / 8 / 2048);
+        return (MOBILE_POWER / mobilePps) / (60*60);
+    }
+
+    public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
+        mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
+        for (int i = 0; i < mPowerBins.length; i++) {
+            mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE, i);
+        }
+        mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
+        mStats = stats;
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        // Add cost of mobile traffic.
+        app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+        app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
+        app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
+        app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+
+        if (app.mobileActive > 0) {
+            // We are tracking when the radio is up, so can use the active time to
+            // determine power use.
+            mTotalAppMobileActiveMs += app.mobileActive;
+            app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
+        } else {
+            // We are not tracking when the radio is up, so must approximate power use
+            // based on the number of packets.
+            app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
+                    * getMobilePowerPerPacket(rawRealtimeUs, statsType);
+        }
+        if (DEBUG && app.mobileRadioPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+                    + (app.mobileRxPackets + app.mobileTxPackets)
+                    + " active time " + app.mobileActive
+                    + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        double power = 0;
+        long signalTimeMs = 0;
+        long noCoverageTimeMs = 0;
+        for (int i = 0; i < mPowerBins.length; i++) {
+            long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
+                    / 1000;
+            final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
+            if (DEBUG && p != 0) {
+                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+                        + BatteryStatsHelper.makemAh(p));
+            }
+            power += p;
+            signalTimeMs += strengthTimeMs;
+            if (i == 0) {
+                noCoverageTimeMs = strengthTimeMs;
+            }
+        }
+
+        final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
+                / 1000;
+        final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
+        if (DEBUG && p != 0) {
+            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
+                    + " power=" + BatteryStatsHelper.makemAh(p));
+        }
+        power += p;
+        long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+        long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
+        if (remainingActiveTimeMs > 0) {
+            power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
+        }
+
+        if (power != 0) {
+            app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
+            app.mobileActive = remainingActiveTimeMs;
+            app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
+            app.mobileRadioPowerMah = power;
+        }
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppMobileActiveMs = 0;
+    }
+
+    public void reset(BatteryStats stats) {
+        reset();
+        mStats = stats;
+    }
+}
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
new file mode 100644
index 0000000..cd69d68
--- /dev/null
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Calculates power use of a device subsystem for an app.
+ */
+public abstract class PowerCalculator {
+    /**
+     * Calculate the amount of power an app used for this subsystem.
+     * @param app The BatterySipper that represents the power use of an app.
+     * @param u The recorded stats for the app.
+     * @param rawRealtimeUs The raw system realtime in microseconds.
+     * @param rawUptimeUs The raw system uptime in microseconds.
+     * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+     *                  {@link BatteryStats#STATS_SINCE_CHARGED}, or
+     *                  {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+     */
+    public abstract void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                                      long rawUptimeUs, int statsType);
+
+    /**
+     * Calculate the remaining power that can not be attributed to an app.
+     * @param app The BatterySipper that will represent this remaining power.
+     * @param stats The BatteryStats object from which to retrieve data.
+     * @param rawRealtimeUs The raw system realtime in microseconds.
+     * @param rawUptimeUs The raw system uptime in microseconds.
+     * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+     *                  {@link BatteryStats#STATS_SINCE_CHARGED}, or
+     *                  {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+     */
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+    }
+
+    /**
+     * Reset any state maintained in this calculator.
+     */
+    public void reset() {
+    }
+}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 944eb5a..7e6706c 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -18,6 +18,7 @@
 
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 
 import com.android.internal.util.XmlUtils;
@@ -75,10 +76,21 @@
      */
     public static final String POWER_WIFI_ACTIVE = "wifi.active";
 
-    /**
-     * Operating voltage of the WiFi controller.
-     */
-    public static final String OPERATING_VOLTAGE_WIFI = "wifi.voltage";
+    //
+    // Updated power constants. These are not estimated, they are real world
+    // currents and voltages for the underlying bluetooth and wifi controllers.
+    //
+
+    public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle";
+    public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx";
+    public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx";
+    public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage";
+
+    public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
+    public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
+    public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
+    public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE =
+            "bluetooth.controller.voltage";
 
     /**
      * Power consumption when GPS is on.
@@ -100,10 +112,6 @@
      */
     public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
 
-    /**
-     * Operating voltage of the Bluetooth controller.
-     */
-    public static final String OPERATING_VOLTAGE_BLUETOOTH = "bluetooth.voltage";
 
     /**
      * Power consumption when screen is on, not including the backlight power.
@@ -162,7 +170,7 @@
      */
     public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
 
-    static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
+    static final HashMap<String, Object> sPowerMap = new HashMap<>();
 
     private static final String TAG_DEVICE = "device";
     private static final String TAG_ITEM = "item";
@@ -180,7 +188,8 @@
 
     private void readPowerValuesFromXml(Context context) {
         int id = com.android.internal.R.xml.power_profile;
-        XmlResourceParser parser = context.getResources().getXml(id);
+        final Resources resources = context.getResources();
+        XmlResourceParser parser = resources.getXml(id);
         boolean parsingArray = false;
         ArrayList<Double> array = new ArrayList<Double>();
         String arrayName = null;
@@ -231,6 +240,36 @@
         } finally {
             parser.close();
         }
+
+        // Now collect other config variables.
+        int[] configResIds = new int[] {
+                com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
+                com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
+                com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
+                com.android.internal.R.integer.config_bluetooth_operating_voltage_mv,
+                com.android.internal.R.integer.config_wifi_idle_receive_cur_ma,
+                com.android.internal.R.integer.config_wifi_active_rx_cur_ma,
+                com.android.internal.R.integer.config_wifi_tx_cur_ma,
+                com.android.internal.R.integer.config_wifi_operating_voltage_mv,
+        };
+
+        String[] configResIdKeys = new String[] {
+                POWER_BLUETOOTH_CONTROLLER_IDLE,
+                POWER_BLUETOOTH_CONTROLLER_RX,
+                POWER_BLUETOOTH_CONTROLLER_TX,
+                POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
+                POWER_WIFI_CONTROLLER_IDLE,
+                POWER_WIFI_CONTROLLER_RX,
+                POWER_WIFI_CONTROLLER_TX,
+                POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE,
+        };
+
+        for (int i = 0; i < configResIds.length; i++) {
+            int value = resources.getInteger(configResIds[i]);
+            if (value > 0) {
+                sPowerMap.put(configResIdKeys[i], (double) value);
+            }
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
new file mode 100644
index 0000000..c98639b
--- /dev/null
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.BatteryStats;
+import android.util.SparseArray;
+
+import java.util.List;
+
+public class SensorPowerCalculator extends PowerCalculator {
+    private final List<Sensor> mSensors;
+    private final double mGpsPowerOn;
+
+    public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) {
+        mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+        mGpsPowerOn = profile.getAveragePower(PowerProfile.POWER_GPS_ON);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        // Process Sensor usage
+        final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+        final int NSE = sensorStats.size();
+        for (int ise = 0; ise < NSE; ise++) {
+            final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise);
+            final int sensorHandle = sensorStats.keyAt(ise);
+            final BatteryStats.Timer timer = sensor.getSensorTime();
+            final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+            switch (sensorHandle) {
+                case BatteryStats.Uid.Sensor.GPS:
+                    app.gpsTimeMs = sensorTime;
+                    app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60);
+                    break;
+                default:
+                    final int sensorsCount = mSensors.size();
+                    for (int i = 0; i < sensorsCount; i++) {
+                        final Sensor s = mSensors.get(i);
+                        if (s.getHandle() == sensorHandle) {
+                            app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60);
+                            break;
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
new file mode 100644
index 0000000..7575010f
--- /dev/null
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class WakelockPowerCalculator extends PowerCalculator {
+    private static final String TAG = "WakelockPowerCalculator";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final double mPowerWakelock;
+    private long mTotalAppWakelockTimeMs = 0;
+
+    public WakelockPowerCalculator(PowerProfile profile) {
+        mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawUptimeUs,
+                             long rawRealtimeUs, int statsType) {
+        long wakeLockTimeUs = 0;
+        final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+                u.getWakelockStats();
+        final int wakelockStatsCount = wakelockStats.size();
+        for (int i = 0; i < wakelockStatsCount; i++) {
+            final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i);
+
+            // Only care about partial wake locks since full wake locks
+            // are canceled when the user turns the screen off.
+            BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+            if (timer != null) {
+                wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
+            }
+        }
+        app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
+        mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+
+        // Add cost of holding a wake lock.
+        app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60);
+        if (DEBUG && app.wakeLockPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
+                    + " power=" + BatteryStatsHelper.makemAh(app.wakeLockPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
+        wakeTimeMillis -= mTotalAppWakelockTimeMs
+                + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+        if (wakeTimeMillis > 0) {
+            final double power = (wakeTimeMillis * mPowerWakelock) / (1000*60*60);
+            if (DEBUG) {
+                Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
+                        + BatteryStatsHelper.makemAh(power));
+            }
+            app.wakeLockTimeMs += wakeTimeMillis;
+            app.wakeLockPowerMah += power;
+        }
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppWakelockTimeMs = 0;
+    }
+}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
new file mode 100644
index 0000000..4e77f6b
--- /dev/null
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * WiFi power calculator for when BatteryStats supports energy reporting
+ * from the WiFi controller.
+ */
+public class WifiPowerCalculator extends PowerCalculator {
+    private final double mIdleCurrentMa;
+    private final double mTxCurrentMa;
+    private final double mRxCurrentMa;
+    private double mTotalAppPowerDrain = 0;
+
+    public WifiPowerCalculator(PowerProfile profile) {
+        mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
+        mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX);
+        mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        final long idleTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
+                statsType);
+        final long txTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType);
+        final long rxTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType);
+        app.wifiRunningTimeMs = idleTime + rxTime + txTime;
+        app.wifiPowerMah =
+                ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
+                / (1000*60*60);
+        mTotalAppPowerDrain += app.wifiPowerMah;
+
+        app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+        app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final long idleTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
+                statsType);
+        final long rxTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME,
+                statsType);
+        final long txTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME,
+                statsType);
+        app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs;
+
+        double powerDrain = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN,
+                statsType) / (1000*60*60);
+        if (powerDrain == 0) {
+            // Some controllers do not report power drain, so we can calculate it here.
+            powerDrain = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+                    + (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
+        }
+        app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain);
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppPowerDrain = 0;
+    }
+}
diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java
new file mode 100644
index 0000000..0172367
--- /dev/null
+++ b/core/java/com/android/internal/os/WifiPowerEstimator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Estimates WiFi power usage based on timers in BatteryStats.
+ */
+public class WifiPowerEstimator extends PowerCalculator {
+    private final double mWifiPowerPerPacket;
+    private final double mWifiPowerOn;
+    private final double mWifiPowerScan;
+    private final double mWifiPowerBatchScan;
+    private long mTotalAppWifiRunningTimeMs = 0;
+
+    public WifiPowerEstimator(PowerProfile profile) {
+        mWifiPowerPerPacket = getWifiPowerPerPacket(profile);
+        mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON);
+        mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN);
+        mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN);
+    }
+
+    /**
+     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+     */
+    private static double getWifiPowerPerPacket(PowerProfile profile) {
+        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
+        final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
+                / 3600;
+        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+        app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+
+        final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
+                * mWifiPowerPerPacket;
+
+        app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
+        mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs;
+        final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60);
+
+        final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType);
+        final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60);
+
+        double wifiBatchScanPower = 0;
+        for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+            final long batchScanTimeMs =
+                    u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
+            final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60);
+            wifiBatchScanPower += batchScanPower;
+        }
+
+        app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType)
+                / 1000;
+        final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn)
+                / (1000*60*60);
+        app.wifiRunningTimeMs = totalRunningTimeMs;
+        app.wifiPowerMah = Math.max(0, powerDrain);
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppWifiRunningTimeMs = 0;
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index eb394c3..1ac1c8a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1487,7 +1487,7 @@
                 NetworkCapabilities.TRANSPORT_WIFI)) {
             timeout = Settings.Global.getInt(mContext.getContentResolver(),
                                              Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
-                                             0);
+                                             5);
             type = ConnectivityManager.TYPE_WIFI;
         } else {
             // do not track any other networks
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 7b542be..b5b62b4 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -210,6 +210,7 @@
 
     private boolean mMobileActivityFromRadio = false;
     private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+    private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
             new RemoteCallbackList<INetworkActivityListener>();
@@ -434,6 +435,16 @@
             }
         }
 
+        if (ConnectivityManager.isNetworkTypeWifi(type)) {
+            if (mLastPowerStateFromWifi != powerState) {
+                mLastPowerStateFromWifi = powerState;
+                try {
+                    getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
         boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
                 || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8db3be..ac70d88 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -40,6 +40,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
@@ -122,6 +123,7 @@
         mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout)
                 * 1000L);
+        mStats.setPowerProfile(new PowerProfile(context));
     }
 
     /**
@@ -541,6 +543,15 @@
         }
     }
 
+    @Override
+    public void noteWifiRadioPowerState(int powerState, long tsNanos) {
+        enforceCallingPermission();
+
+        // There was a change in WiFi power state.
+        // Collect data now for the past activity.
+        mHandler.scheduleSync();
+    }
+
     public void noteWifiRunning(WorkSource ws) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -1095,13 +1106,29 @@
                 result.mTimestamp = info.getTimeStamp();
                 result.mStackState = info.getStackState();
                 result.mControllerTxTimeMs =
-                        info.getControllerTxTimeMillis()- mLastInfo.mControllerTxTimeMs;
+                        info.mControllerTxTimeMs - mLastInfo.mControllerTxTimeMs;
                 result.mControllerRxTimeMs =
-                        info.getControllerRxTimeMillis() - mLastInfo.mControllerRxTimeMs;
-                result.mControllerIdleTimeMs =
-                        info.getControllerIdleTimeMillis() - mLastInfo.mControllerIdleTimeMs;
+                        info.mControllerRxTimeMs - mLastInfo.mControllerRxTimeMs;
                 result.mControllerEnergyUsed =
-                        info.getControllerEnergyUsed() - mLastInfo.mControllerEnergyUsed;
+                        info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed;
+
+                // WiFi calculates the idle time as a difference from the on time and the various
+                // Rx + Tx times. There seems to be some missing time there because this sometimes
+                // becomes negative. Just cap it at 0 and move on.
+                result.mControllerIdleTimeMs =
+                        Math.max(0, info.mControllerIdleTimeMs - mLastInfo.mControllerIdleTimeMs);
+
+                if (result.mControllerTxTimeMs < 0 ||
+                        result.mControllerRxTimeMs < 0) {
+                    // The stats were reset by the WiFi system (which is why our delta is negative).
+                    // Returns the unaltered stats.
+                    result.mControllerEnergyUsed = info.mControllerEnergyUsed;
+                    result.mControllerRxTimeMs = info.mControllerRxTimeMs;
+                    result.mControllerTxTimeMs = info.mControllerTxTimeMs;
+                    result.mControllerIdleTimeMs = info.mControllerIdleTimeMs;
+
+                    Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + result);
+                }
                 mLastInfo = info;
                 return result;
             }
@@ -1133,6 +1160,12 @@
      */
     void updateExternalStats() {
         synchronized (mExternalStatsLock) {
+            if (mContext == null) {
+                // We haven't started yet (which means the BatteryStatsImpl object has
+                // no power profile. Don't consume data we can't compute yet.
+                return;
+            }
+
             final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked();
             final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked();
             synchronized (mStats) {