Retrieve WiFi info outside of BatteryStatsImpl

In order to not deadlock the system, we need to retrieve
WiFi energy info outside of the BatteryStats lock. We do this,
then pass that data down to BatteryStatsImpl to process.

b/19729960

Change-Id: Ib8beba1d5ac81d89144d502c4b688d0a88c5b102
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1a06e0a..8b3ecae 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3233,9 +3233,9 @@
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
-        final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
-        final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
-        final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+        final long wifiIdleTimeMs = getWifiControllerActivity(CONTROLLER_IDLE_TIME, which);
+        final long wifiRxTimeMs = getWifiControllerActivity(CONTROLLER_RX_TIME, which);
+        final long wifiTxTimeMs = getWifiControllerActivity(CONTROLLER_TX_TIME, which);
         final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs;
 
         sb.setLength(0);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2c34ded..93dc995 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,18 +16,14 @@
 
 package com.android.internal.os;
 
-import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
-
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
-import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiManager;
 import android.os.BadParcelableException;
@@ -42,8 +38,6 @@
 import android.os.ParcelFormatException;
 import android.os.Parcelable;
 import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.WorkSource;
@@ -65,13 +59,14 @@
 import android.util.Xml;
 import android.view.Display;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
+import com.android.server.NetworkManagementSocketTagger;
+import libcore.util.EmptyArray;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -132,6 +127,9 @@
     static final int MSG_REPORT_POWER_CHANGE = 2;
     static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
 
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
         public void batteryPowerChanged(boolean onBattery);
@@ -160,7 +158,12 @@
         }
     }
 
+    public interface ExternalStatsSync {
+        void scheduleSync();
+    }
+
     public final MyHandler mHandler;
+    private final ExternalStatsSync mExternalSync;
 
     private BatteryCallback mCallback;
 
@@ -330,7 +333,7 @@
 
     int mPhoneSignalStrengthBin = -1;
     int mPhoneSignalStrengthBinRaw = -1;
-    final StopwatchTimer[] mPhoneSignalStrengthsTimer = 
+    final StopwatchTimer[] mPhoneSignalStrengthsTimer =
             new StopwatchTimer[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
 
     StopwatchTimer mPhoneSignalScanningTimer;
@@ -445,18 +448,17 @@
     private int mLoadedNumConnectivityChange;
     private int mUnpluggedNumConnectivityChange;
 
+    private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
+
     /*
      * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
      */
-    private final HashMap<String, SamplingTimer> mKernelWakelockStats =
-            new HashMap<String, SamplingTimer>();
+    private final HashMap<String, SamplingTimer> mKernelWakelockStats = new HashMap<>();
 
     public Map<String, ? extends Timer> getKernelWakelockStats() {
         return mKernelWakelockStats;
     }
 
-    private static int sKernelWakelockUpdateVersion = 0;
-
     String mLastWakeupReason = null;
     long mLastWakeupUptimeMs = 0;
     private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
@@ -465,55 +467,12 @@
         return mWakeupReasonStats;
     }
 
-    private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
-        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
-                              Process.PROC_QUOTES,
-        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
-    };
-
-    private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
-        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE|
-                              Process.PROC_OUT_LONG,                  // 1: count
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE
-                             |Process.PROC_OUT_LONG,                  // 6: totalTime
-    };
-
-    private final String[] mProcWakelocksName = new String[3];
-    private final long[] mProcWakelocksData = new long[3];
-
-    /*
-     * Used as a buffer for reading in data from /proc/wakelocks before it is processed and added
-     * to mKernelWakelockStats.
-     */
-    private final Map<String, KernelWakelockStats> mProcWakelockFileStats = new HashMap<>();
-
-    private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
-    private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
-    private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
-    private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
-    private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
-    private NetworkStats mTmpNetworkStats;
-    private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
-
-    @GuardedBy("this")
-    private String[] mMobileIfaces = new String[0];
-    @GuardedBy("this")
-    private String[] mWifiIfaces = new String[0];
-
     public BatteryStatsImpl() {
         mFile = null;
         mCheckinFile = null;
         mDailyFile = null;
         mHandler = null;
+        mExternalSync = null;
         clearHistoryLocked();
     }
 
@@ -523,7 +482,7 @@
     }
 
     static class TimeBase {
-        private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>();
+        private final ArrayList<TimeBaseObs> mObservers = new ArrayList<>();
 
         private long mUptime;
         private long mRealtime;
@@ -1778,147 +1737,6 @@
         return timer;
     }
 
-    private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
-
-        FileInputStream is;
-        byte[] buffer = new byte[32*1024];
-        int len;
-        boolean wakeup_sources;
-
-        try {
-            try {
-                is = new FileInputStream("/d/wakeup_sources");
-                wakeup_sources = true;
-            } catch (java.io.FileNotFoundException e) {
-                try {
-                    is = new FileInputStream("/proc/wakelocks");
-                    wakeup_sources = false;
-                } catch (java.io.FileNotFoundException e2) {
-                    return null;
-                }
-            }
-
-            len = is.read(buffer);
-            is.close();
-        } catch (java.io.IOException e) {
-            return null;
-        }
-
-        if (len > 0) {
-            if (len >= buffer.length) {
-                Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
-            }
-            int i;
-            for (i=0; i<len; i++) {
-                if (buffer[i] == '\0') {
-                    len = i;
-                    break;
-                }
-            }
-        }
-
-        return parseProcWakelocks(buffer, len, wakeup_sources);
-    }
-
-    private final Map<String, KernelWakelockStats> parseProcWakelocks(
-            byte[] wlBuffer, int len, boolean wakeup_sources) {
-        String name;
-        int count;
-        long totalTime;
-        int startIndex;
-        int endIndex;
-        int numUpdatedWlNames = 0;
-
-        // Advance past the first line.
-        int i;
-        for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
-        startIndex = endIndex = i + 1;
-
-        synchronized(this) {
-            Map<String, KernelWakelockStats> m = mProcWakelockFileStats;
-
-            sKernelWakelockUpdateVersion++;
-            while (endIndex < len) {
-                for (endIndex=startIndex;
-                        endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
-                        endIndex++);
-                endIndex++; // endIndex is an exclusive upper bound.
-                // Don't go over the end of the buffer, Process.parseProcLine might
-                // write to wlBuffer[endIndex]
-                if (endIndex >= (len - 1) ) {
-                    return m;
-                }
-
-                String[] nameStringArray = mProcWakelocksName;
-                long[] wlData = mProcWakelocksData;
-                // Stomp out any bad characters since this is from a circular buffer
-                // A corruption is seen sometimes that results in the vm crashing
-                // This should prevent crashes and the line will probably fail to parse
-                for (int j = startIndex; j < endIndex; j++) {
-                    if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
-                }
-                boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
-                        wakeup_sources ? WAKEUP_SOURCES_FORMAT :
-                                         PROC_WAKELOCKS_FORMAT,
-                        nameStringArray, wlData, null);
-
-                name = nameStringArray[0];
-                count = (int) wlData[1];
-
-                if (wakeup_sources) {
-                        // convert milliseconds to microseconds
-                        totalTime = wlData[2] * 1000;
-                } else {
-                        // convert nanoseconds to microseconds with rounding.
-                        totalTime = (wlData[2] + 500) / 1000;
-                }
-
-                if (parsed && name.length() > 0) {
-                    if (!m.containsKey(name)) {
-                        m.put(name, new KernelWakelockStats(count, totalTime,
-                                sKernelWakelockUpdateVersion));
-                        numUpdatedWlNames++;
-                    } else {
-                        KernelWakelockStats kwlStats = m.get(name);
-                        if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
-                            kwlStats.mCount += count;
-                            kwlStats.mTotalTime += totalTime;
-                        } else {
-                            kwlStats.mCount = count;
-                            kwlStats.mTotalTime = totalTime;
-                            kwlStats.mVersion = sKernelWakelockUpdateVersion;
-                            numUpdatedWlNames++;
-                        }
-                    }
-                }
-                startIndex = endIndex;
-            }
-
-            if (m.size() != numUpdatedWlNames) {
-                // Don't report old data.
-                Iterator<KernelWakelockStats> itr = m.values().iterator();
-                while (itr.hasNext()) {
-                    if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
-                        itr.remove();
-                    }
-                }
-            }
-            return m;
-        }
-    }
-
-    private class KernelWakelockStats {
-        public int mCount;
-        public long mTotalTime;
-        public int mVersion;
-
-        KernelWakelockStats(int count, long totalTime, int version) {
-            mCount = count;
-            mTotalTime = totalTime;
-            mVersion = version;
-        }
-    }
-
     /*
      * Get the KernelWakelockTimer associated with name, and create a new one if one
      * doesn't already exist.
@@ -3390,7 +3208,7 @@
                 mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
             } else {
                 mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
-                updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs);
+                updateMobileRadioStateLocked(realElapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
             }
         }
@@ -3728,6 +3546,7 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mWifiOn = true;
             mWifiOnTimer.startRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked();
         }
     }
 
@@ -3741,6 +3560,7 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mWifiOn = false;
             mWifiOnTimer.stopRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked();
         }
     }
 
@@ -3903,6 +3723,7 @@
                 int uid = mapUid(ws.get(i));
                 getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
             }
+            scheduleSyncExternalStatsLocked();
         } else {
             Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
         }
@@ -3941,6 +3762,7 @@
                 int uid = mapUid(ws.get(i));
                 getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
             }
+            scheduleSyncExternalStatsLocked();
         } else {
             Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
         }
@@ -3955,6 +3777,7 @@
             }
             mWifiState = wifiState;
             mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked();
         }
     }
 
@@ -4026,6 +3849,7 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mBluetoothOn = true;
             mBluetoothOnTimer.startRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked();
         }
     }
 
@@ -4039,6 +3863,7 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mBluetoothOn = false;
             mBluetoothOnTimer.stopRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked();
         }
     }
 
@@ -4065,6 +3890,7 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            scheduleSyncExternalStatsLocked();
         }
         mWifiFullLockNesting++;
         getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime);
@@ -4080,6 +3906,7 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            scheduleSyncExternalStatsLocked();
         }
         getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime);
     }
@@ -4137,6 +3964,7 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            scheduleSyncExternalStatsLocked();
         }
         mWifiMulticastNesting++;
         getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
@@ -4152,6 +3980,7 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            scheduleSyncExternalStatsLocked();
         }
         getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
     }
@@ -4259,7 +4088,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.
-        updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+        updateMobileRadioStateLocked(SystemClock.elapsedRealtime());
+        updateWifiStateLocked(null);
     }
 
     @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
@@ -5949,7 +5779,7 @@
                     Slog.w(TAG, "File corrupt: too many excessive power entries " + N);
                     return false;
                 }
-                
+
                 mExcessivePower = new ArrayList<ExcessivePower>();
                 for (int i=0; i<N; i++) {
                     ExcessivePower ew = new ExcessivePower();
@@ -6727,7 +6557,7 @@
         }
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler) {
+    public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync) {
         if (systemDir != null) {
             mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
                     new File(systemDir, "batterystats.bin.tmp"));
@@ -6736,6 +6566,7 @@
         }
         mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
+        mExternalSync = externalSync;
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6808,6 +6639,7 @@
         mCheckinFile = null;
         mDailyFile = null;
         mHandler = null;
+        mExternalSync = null;
         clearHistoryLocked();
         readFromParcel(p);
     }
@@ -7479,21 +7311,233 @@
             mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
         }
     }
-    
+
     public void pullPendingStateUpdatesLocked() {
-        updateKernelWakelocksLocked();
-        updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
-        // TODO(adamlesinski): enable when bluedroid stops deadlocking. b/19248786
-        // updateBluetoothControllerActivityLocked();
-        // TODO(adamlesinski): disabled to avoid deadlock. Need to change how external
-        // data is pulled/accessed from BatteryStats. b/19729960
-        // updateWifiControllerActivityLocked();
         if (mOnBatteryInternal) {
             final boolean screenOn = mScreenState == Display.STATE_ON;
             updateDischargeScreenLevelsLocked(screenOn, screenOn);
         }
     }
 
+    private String[] mMobileIfaces = EmptyArray.STRING;
+    private String[] mWifiIfaces = EmptyArray.STRING;
+
+    private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
+
+    private static final int NETWORK_STATS_LAST = 0;
+    private static final int NETWORK_STATS_NEXT = 1;
+    private static final int NETWORK_STATS_DELTA = 2;
+
+    private final NetworkStats[] mMobileNetworkStats = new NetworkStats[] {
+            new NetworkStats(SystemClock.elapsedRealtime(), 50),
+            new NetworkStats(SystemClock.elapsedRealtime(), 50),
+            new NetworkStats(SystemClock.elapsedRealtime(), 50)
+    };
+
+    private final NetworkStats[] mWifiNetworkStats = new NetworkStats[] {
+            new NetworkStats(SystemClock.elapsedRealtime(), 50),
+            new NetworkStats(SystemClock.elapsedRealtime(), 50),
+            new NetworkStats(SystemClock.elapsedRealtime(), 50)
+    };
+
+    /**
+     * Retrieves the delta of network stats for the given network ifaces. Uses networkStatsBuffer
+     * as a buffer of NetworkStats objects to cycle through when computing deltas.
+     */
+    private NetworkStats getNetworkStatsDeltaLocked(String[] ifaces,
+                                                    NetworkStats[] networkStatsBuffer)
+            throws IOException {
+        if (!SystemProperties.getBoolean(NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED,
+                false)) {
+            return null;
+        }
+
+        final NetworkStats stats = mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL,
+                ifaces, NetworkStats.TAG_NONE, networkStatsBuffer[NETWORK_STATS_NEXT]);
+        networkStatsBuffer[NETWORK_STATS_DELTA] = NetworkStats.subtract(stats,
+                networkStatsBuffer[NETWORK_STATS_LAST], null, null,
+                networkStatsBuffer[NETWORK_STATS_DELTA]);
+        networkStatsBuffer[NETWORK_STATS_NEXT] = networkStatsBuffer[NETWORK_STATS_LAST];
+        networkStatsBuffer[NETWORK_STATS_LAST] = stats;
+        return networkStatsBuffer[NETWORK_STATS_DELTA];
+    }
+
+    /**
+     * Distribute WiFi energy info and network traffic to apps.
+     * @param info The energy information from the WiFi controller.
+     */
+    public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) {
+        final NetworkStats delta;
+        try {
+            delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats);
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to get wifi network stats", e);
+            return;
+        }
+
+        if (!mOnBatteryInternal) {
+            return;
+        }
+
+        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) {
+                    Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
+                            + " tx=" + entry.txBytes);
+                }
+
+                if (entry.rxBytes == 0 || entry.txBytes == 0) {
+                    continue;
+                }
+
+                final Uid u = getUidStatsLocked(mapUid(entry.uid));
+                u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+                        entry.rxPackets);
+                u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+                        entry.txPackets);
+
+                mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+                        entry.rxBytes);
+                mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+                        entry.txBytes);
+                mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+                        entry.rxPackets);
+                mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+                        entry.txPackets);
+            }
+        }
+
+        if (info != null) {
+            // Update WiFi controller stats.
+            mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+                    info.getControllerRxTimeMillis());
+            mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+                    info.getControllerTxTimeMillis());
+            mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+                    info.getControllerIdleTimeMillis());
+            mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+                    info.getControllerEnergyUsed());
+        }
+    }
+
+    /**
+     * Distribute Cell radio energy info and network traffic to apps.
+     */
+    public void updateMobileRadioStateLocked(long elapsedRealtimeMs) {
+        final NetworkStats delta;
+
+        try {
+            delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats);
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to get mobile network stats", e);
+            return;
+        }
+
+        if (delta == null || !mOnBatteryInternal) {
+            return;
+        }
+
+        long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(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;
+
+            final Uid u = getUidStatsLocked(mapUid(entry.uid));
+            u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
+                    entry.rxPackets);
+            u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
+                    entry.txPackets);
+
+            if (radioTime > 0) {
+                // Distribute total radio active time in to this app.
+                long appPackets = entry.rxPackets + entry.txPackets;
+                long appRadioTime = (radioTime*appPackets)/totalPackets;
+                u.noteMobileRadioActiveTimeLocked(appRadioTime);
+                // Remove this app from the totals, so that we don't lose any time
+                // due to rounding.
+                radioTime -= appRadioTime;
+                totalPackets -= appPackets;
+            }
+
+            mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+                    entry.rxBytes);
+            mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+                    entry.txBytes);
+            mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+                    entry.rxPackets);
+            mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+                    entry.txPackets);
+        }
+
+        if (radioTime > 0) {
+            // Whoops, there is some radio time we can't blame on an app!
+            mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
+            mMobileRadioActiveUnknownCount.addCountLocked(1);
+        }
+    }
+
+    /**
+     * Distribute Bluetooth energy info and network traffic to apps.
+     * @param info The energy information from the bluetooth controller.
+     */
+    public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
+        if (info != null && mOnBatteryInternal) {
+            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(
+                    info.getControllerEnergyUsed());
+        }
+    }
+
+    /**
+     * Read and distribute kernel wake lock use across apps.
+     */
+    public void updateKernelWakelocksLocked() {
+        final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(
+                mTmpWakelockStats);
+        if (wakelockStats == null) {
+            // Not crashing might make board bringup easier.
+            Slog.w(TAG, "Couldn't get kernel wake lock stats");
+            return;
+        }
+
+        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+            String name = ent.getKey();
+            KernelWakelockStats.Entry kws = ent.getValue();
+
+            SamplingTimer kwlt = mKernelWakelockStats.get(name);
+            if (kwlt == null) {
+                kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
+                        true /* track reported val */);
+                mKernelWakelockStats.put(name, kwlt);
+            }
+            kwlt.updateCurrentReportedCount(kws.mCount);
+            kwlt.updateCurrentReportedTotalTime(kws.mTotalTime);
+            kwlt.setUpdateVersion(kws.mVersion);
+        }
+
+        if (wakelockStats.size() != mKernelWakelockStats.size()) {
+            // Set timers to stale if they didn't appear in /proc/wakelocks this time.
+            for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+                SamplingTimer st = ent.getValue();
+                if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) {
+                    st.setStale();
+                }
+            }
+        }
+    }
+
     void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
             final int oldStatus, final int level) {
         boolean doWrite = false;
@@ -7647,340 +7691,132 @@
         }
     }
 
+    private void scheduleSyncExternalStatsLocked() {
+        if (mExternalSync != null) {
+            mExternalSync.scheduleSync();
+        }
+    }
+
     // This should probably be exposed in the API, though it's not critical
-    private static final int BATTERY_PLUGGED_NONE = 0;
+    public static final int BATTERY_PLUGGED_NONE = 0;
 
-    public void setBatteryState(int status, int health, int plugType, int level,
+    public void setBatteryStateLocked(int status, int health, int plugType, int level,
             int temp, int volt) {
-        synchronized(this) {
-            final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
-            final long uptime = SystemClock.uptimeMillis();
-            final long elapsedRealtime = SystemClock.elapsedRealtime();
-            if (!mHaveBatteryLevel) {
-                mHaveBatteryLevel = true;
-                // We start out assuming that the device is plugged in (not
-                // on battery).  If our first report is now that we are indeed
-                // plugged in, then twiddle our state to correctly reflect that
-                // since we won't be going through the full setOnBattery().
-                if (onBattery == mOnBattery) {
-                    if (onBattery) {
-                        mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-                    } else {
-                        mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-                    }
-                }
-                mHistoryCur.batteryStatus = (byte)status;
-                mHistoryCur.batteryLevel = (byte)level;
-                mMaxChargeStepLevel = mMinDischargeStepLevel =
-                        mLastChargeStepLevel = mLastDischargeStepLevel = level;
-            } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
-                recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
-            }
-            int oldStatus = mHistoryCur.batteryStatus;
-            if (onBattery) {
-                mDischargeCurrentLevel = level;
-                if (!mRecordingHistory) {
-                    mRecordingHistory = true;
-                    startRecordingHistory(elapsedRealtime, uptime, true);
-                }
-            } else if (level < 96) {
-                if (!mRecordingHistory) {
-                    mRecordingHistory = true;
-                    startRecordingHistory(elapsedRealtime, uptime, true);
-                }
-            }
-            mCurrentBatteryLevel = level;
-            if (mDischargePlugLevel < 0) {
-                mDischargePlugLevel = level;
-            }
-            if (onBattery != mOnBattery) {
-                mHistoryCur.batteryLevel = (byte)level;
-                mHistoryCur.batteryStatus = (byte)status;
-                mHistoryCur.batteryHealth = (byte)health;
-                mHistoryCur.batteryPlugType = (byte)plugType;
-                mHistoryCur.batteryTemperature = (short)temp;
-                mHistoryCur.batteryVoltage = (char)volt;
-                setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level);
-            } else {
-                boolean changed = false;
-                if (mHistoryCur.batteryLevel != level) {
-                    mHistoryCur.batteryLevel = (byte)level;
-                    changed = true;
-                }
-                if (mHistoryCur.batteryStatus != status) {
-                    mHistoryCur.batteryStatus = (byte)status;
-                    changed = true;
-                }
-                if (mHistoryCur.batteryHealth != health) {
-                    mHistoryCur.batteryHealth = (byte)health;
-                    changed = true;
-                }
-                if (mHistoryCur.batteryPlugType != plugType) {
-                    mHistoryCur.batteryPlugType = (byte)plugType;
-                    changed = true;
-                }
-                if (temp >= (mHistoryCur.batteryTemperature+10)
-                        || temp <= (mHistoryCur.batteryTemperature-10)) {
-                    mHistoryCur.batteryTemperature = (short)temp;
-                    changed = true;
-                }
-                if (volt > (mHistoryCur.batteryVoltage+20)
-                        || volt < (mHistoryCur.batteryVoltage-20)) {
-                    mHistoryCur.batteryVoltage = (char)volt;
-                    changed = true;
-                }
-                if (changed) {
-                    addHistoryRecordLocked(elapsedRealtime, uptime);
-                }
-                long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
-                        | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
-                        | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
+        final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+        final long uptime = SystemClock.uptimeMillis();
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        if (!mHaveBatteryLevel) {
+            mHaveBatteryLevel = true;
+            // We start out assuming that the device is plugged in (not
+            // on battery).  If our first report is now that we are indeed
+            // plugged in, then twiddle our state to correctly reflect that
+            // since we won't be going through the full setOnBattery().
+            if (onBattery == mOnBattery) {
                 if (onBattery) {
-                    if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
-                        mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
-                                modeBits, elapsedRealtime);
-                        mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
-                                modeBits, elapsedRealtime);
-                        mLastDischargeStepLevel = level;
-                        mMinDischargeStepLevel = level;
-                        mInitStepMode = mCurStepMode;
-                        mModStepMode = 0;
-                    }
+                    mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
                 } else {
-                    if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
-                        mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
-                                modeBits, elapsedRealtime);
-                        mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
-                                modeBits, elapsedRealtime);
-                        mLastChargeStepLevel = level;
-                        mMaxChargeStepLevel = level;
-                        mInitStepMode = mCurStepMode;
-                        mModStepMode = 0;
-                    }
+                    mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
                 }
             }
-            if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
-                // We don't record history while we are plugged in and fully charged.
-                // The next time we are unplugged, history will be cleared.
-                mRecordingHistory = DEBUG;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryLevel = (byte)level;
+            mMaxChargeStepLevel = mMinDischargeStepLevel =
+                    mLastChargeStepLevel = mLastDischargeStepLevel = level;
+        } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+            recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
+        }
+        int oldStatus = mHistoryCur.batteryStatus;
+        if (onBattery) {
+            mDischargeCurrentLevel = level;
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtime, uptime, true);
+            }
+        } else if (level < 96) {
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtime, uptime, true);
             }
         }
-    }
-
-    public void updateKernelWakelocksLocked() {
-        Map<String, KernelWakelockStats> m = readKernelWakelockStats();
-
-        if (m == null) {
-            // Not crashing might make board bringup easier.
-            Slog.w(TAG, "Couldn't get kernel wake lock stats");
-            return;
+        mCurrentBatteryLevel = level;
+        if (mDischargePlugLevel < 0) {
+            mDischargePlugLevel = level;
         }
+        if (onBattery != mOnBattery) {
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryHealth = (byte)health;
+            mHistoryCur.batteryPlugType = (byte)plugType;
+            mHistoryCur.batteryTemperature = (short)temp;
+            mHistoryCur.batteryVoltage = (char)volt;
+            setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level);
+        } else {
+            boolean changed = false;
+            if (mHistoryCur.batteryLevel != level) {
+                mHistoryCur.batteryLevel = (byte)level;
+                changed = true;
 
-        for (Map.Entry<String, KernelWakelockStats> ent : m.entrySet()) {
-            String name = ent.getKey();
-            KernelWakelockStats kws = ent.getValue();
-
-            SamplingTimer kwlt = mKernelWakelockStats.get(name);
-            if (kwlt == null) {
-                kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
-                        true /* track reported val */);
-                mKernelWakelockStats.put(name, kwlt);
+                // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
+                // which will pull external stats.
+                scheduleSyncExternalStatsLocked();
             }
-            kwlt.updateCurrentReportedCount(kws.mCount);
-            kwlt.updateCurrentReportedTotalTime(kws.mTotalTime);
-            kwlt.setUpdateVersion(sKernelWakelockUpdateVersion);
-        }
-
-        if (m.size() != mKernelWakelockStats.size()) {
-            // Set timers to stale if they didn't appear in /proc/wakelocks this time.
-            for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
-                SamplingTimer st = ent.getValue();
-                if (st.getUpdateVersion() != sKernelWakelockUpdateVersion) {
-                    st.setStale();
+            if (mHistoryCur.batteryStatus != status) {
+                mHistoryCur.batteryStatus = (byte)status;
+                changed = true;
+            }
+            if (mHistoryCur.batteryHealth != health) {
+                mHistoryCur.batteryHealth = (byte)health;
+                changed = true;
+            }
+            if (mHistoryCur.batteryPlugType != plugType) {
+                mHistoryCur.batteryPlugType = (byte)plugType;
+                changed = true;
+            }
+            if (temp >= (mHistoryCur.batteryTemperature+10)
+                    || temp <= (mHistoryCur.batteryTemperature-10)) {
+                mHistoryCur.batteryTemperature = (short)temp;
+                changed = true;
+            }
+            if (volt > (mHistoryCur.batteryVoltage+20)
+                    || volt < (mHistoryCur.batteryVoltage-20)) {
+                mHistoryCur.batteryVoltage = (char)volt;
+                changed = true;
+            }
+            if (changed) {
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+            long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+                    | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+                    | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
+            if (onBattery) {
+                if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
+                    mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+                            modeBits, elapsedRealtime);
+                    mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+                            modeBits, elapsedRealtime);
+                    mLastDischargeStepLevel = level;
+                    mMinDischargeStepLevel = level;
+                    mInitStepMode = mCurStepMode;
+                    mModStepMode = 0;
+                }
+            } else {
+                if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
+                    mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+                            modeBits, elapsedRealtime);
+                    mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+                            modeBits, elapsedRealtime);
+                    mLastChargeStepLevel = level;
+                    mMaxChargeStepLevel = level;
+                    mInitStepMode = mCurStepMode;
+                    mModStepMode = 0;
                 }
             }
         }
-    }
-
-    static final int NET_UPDATE_MOBILE = 1<<0;
-    static final int NET_UPDATE_WIFI = 1<<1;
-    static final int NET_UPDATE_ALL = 0xffff;
-
-    private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) {
-        if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return;
-
-        if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) {
-            final NetworkStats snapshot;
-            final NetworkStats last = mCurMobileSnapshot;
-            try {
-                snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
-                        mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot);
-            } catch (IOException e) {
-                Log.wtf(TAG, "Failed to read mobile network stats", e);
-                return;
-            }
-
-            mCurMobileSnapshot = snapshot;
-            mLastMobileSnapshot = last;
-
-            if (mOnBatteryInternal) {
-                final NetworkStats delta = NetworkStats.subtract(snapshot, last,
-                        null, null, mTmpNetworkStats);
-                mTmpNetworkStats = delta;
-
-                long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(
-                        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;
-
-                    final Uid u = getUidStatsLocked(mapUid(entry.uid));
-                    u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
-                            entry.rxPackets);
-                    u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
-                            entry.txPackets);
-
-                    if (radioTime > 0) {
-                        // Distribute total radio active time in to this app.
-                        long appPackets = entry.rxPackets + entry.txPackets;
-                        long appRadioTime = (radioTime*appPackets)/totalPackets;
-                        u.noteMobileRadioActiveTimeLocked(appRadioTime);
-                        // Remove this app from the totals, so that we don't lose any time
-                        // due to rounding.
-                        radioTime -= appRadioTime;
-                        totalPackets -= appPackets;
-                    }
-
-                    mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
-                            entry.rxBytes);
-                    mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
-                            entry.txBytes);
-                    mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
-                            entry.rxPackets);
-                    mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
-                            entry.txPackets);
-                }
-
-                if (radioTime > 0) {
-                    // Whoops, there is some radio time we can't blame on an app!
-                    mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
-                    mMobileRadioActiveUnknownCount.addCountLocked(1);
-                }
-            }
+        if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
+            // We don't record history while we are plugged in and fully charged.
+            // The next time we are unplugged, history will be cleared.
+            mRecordingHistory = DEBUG;
         }
-
-        if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) {
-            final NetworkStats snapshot;
-            final NetworkStats last = mCurWifiSnapshot;
-            try {
-                snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
-                        mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot);
-            } catch (IOException e) {
-                Log.wtf(TAG, "Failed to read wifi network stats", e);
-                return;
-            }
-
-            mCurWifiSnapshot = snapshot;
-            mLastWifiSnapshot = last;
-
-            if (mOnBatteryInternal) {
-                final NetworkStats delta = NetworkStats.subtract(snapshot, last,
-                        null, null, mTmpNetworkStats);
-                mTmpNetworkStats = delta;
-
-                final int size = delta.size();
-                for (int i = 0; i < size; i++) {
-                    final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
-
-                    if (DEBUG) {
-                        final NetworkStats.Entry cur = snapshot.getValues(i, null);
-                        Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
-                                + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes
-                                + " tx=" + cur.txBytes);
-                    }
-
-                    if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
-
-                    final Uid u = getUidStatsLocked(mapUid(entry.uid));
-                    u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
-                            entry.rxPackets);
-                    u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
-                            entry.txPackets);
-
-                    mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
-                            entry.rxBytes);
-                    mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
-                            entry.txBytes);
-                    mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
-                            entry.rxPackets);
-                    mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
-                            entry.txPackets);
-                }
-            }
-        }
-    }
-
-    private void updateBluetoothControllerActivityLocked() {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter == null) {
-            return;
-        }
-
-        // We read the data even if we are not on battery. Each read clears
-        // the previous data, so we must always read to make sure the
-        // data is for the current interval.
-        BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
-                BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
-        if (info == null || !info.isValid() || !mOnBatteryInternal) {
-            // Bad info or we are not on battery.
-            return;
-        }
-
-        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(
-                info.getControllerEnergyUsed());
-    }
-
-    private void updateWifiControllerActivityLocked() {
-        IWifiManager wifiManager = IWifiManager.Stub.asInterface(
-                ServiceManager.getService(Context.WIFI_SERVICE));
-        if (wifiManager == null) {
-            return;
-        }
-
-        WifiActivityEnergyInfo info;
-        try {
-            // We read the data even if we are not on battery. Each read clears
-            // the previous data, so we must always read to make sure the
-            // data is for the current interval.
-            info = wifiManager.reportActivityInfo();
-        } catch (RemoteException e) {
-            // Nothing to report, WiFi is dead.
-            return;
-        }
-
-        if (info == null || !info.isValid() || !mOnBatteryInternal) {
-            // Bad info or we are not on battery.
-            return;
-        }
-
-        mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
-                info.getControllerRxTimeMillis());
-        mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
-                info.getControllerTxTimeMillis());
-        mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
-                info.getControllerIdleTimeMillis());
-        mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
-                info.getControllerEnergyUsed());
     }
 
     public long getAwakeTimeBattery() {
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
new file mode 100644
index 0000000..768d586
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -0,0 +1,192 @@
+/*
+ * 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.Process;
+import android.util.Slog;
+
+import java.io.FileInputStream;
+import java.util.Iterator;
+
+/**
+ * Reads and parses wakelock stats from the kernel (/proc/wakelocks).
+ */
+public class KernelWakelockReader {
+    private static final String TAG = "KernelWakelockReader";
+    private static int sKernelWakelockUpdateVersion = 0;
+    private static final String sWakelockFile = "/proc/wakelocks";
+    private static final String sWakeupSourceFile = "/d/wakeup_sources";
+
+    private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
+        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
+                              Process.PROC_QUOTES,
+        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
+    };
+
+    private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
+        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE|
+                              Process.PROC_OUT_LONG,                  // 1: count
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE
+                             |Process.PROC_OUT_LONG,                  // 6: totalTime
+    };
+
+    private final String[] mProcWakelocksName = new String[3];
+    private final long[] mProcWakelocksData = new long[3];
+
+    /**
+     * Reads kernel wakelock stats and updates the staleStats with the new information.
+     * @param staleStats Existing object to update.
+     * @return the updated data.
+     */
+    public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
+        byte[] buffer = new byte[32*1024];
+        int len;
+        boolean wakeup_sources;
+
+        try {
+            FileInputStream is;
+            try {
+                is = new FileInputStream(sWakeupSourceFile);
+                wakeup_sources = true;
+            } catch (java.io.FileNotFoundException e) {
+                try {
+                    is = new FileInputStream(sWakelockFile);
+                    wakeup_sources = false;
+                } catch (java.io.FileNotFoundException e2) {
+                    return null;
+                }
+            }
+
+            len = is.read(buffer);
+            is.close();
+        } catch (java.io.IOException e) {
+            return null;
+        }
+
+        if (len > 0) {
+            if (len >= buffer.length) {
+                Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+            }
+            int i;
+            for (i=0; i<len; i++) {
+                if (buffer[i] == '\0') {
+                    len = i;
+                    break;
+                }
+            }
+        }
+        return parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+    }
+
+    /**
+     * Reads the wakelocks and updates the staleStats with the new information.
+     */
+    private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
+                                                   final KernelWakelockStats staleStats) {
+        String name;
+        int count;
+        long totalTime;
+        int startIndex;
+        int endIndex;
+        int numUpdatedWlNames = 0;
+
+        // Advance past the first line.
+        int i;
+        for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
+        startIndex = endIndex = i + 1;
+
+        synchronized(this) {
+            sKernelWakelockUpdateVersion++;
+            while (endIndex < len) {
+                for (endIndex=startIndex;
+                        endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
+                        endIndex++);
+                endIndex++; // endIndex is an exclusive upper bound.
+                // Don't go over the end of the buffer, Process.parseProcLine might
+                // write to wlBuffer[endIndex]
+                if (endIndex >= (len - 1) ) {
+                    return staleStats;
+                }
+
+                String[] nameStringArray = mProcWakelocksName;
+                long[] wlData = mProcWakelocksData;
+                // Stomp out any bad characters since this is from a circular buffer
+                // A corruption is seen sometimes that results in the vm crashing
+                // This should prevent crashes and the line will probably fail to parse
+                for (int j = startIndex; j < endIndex; j++) {
+                    if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
+                }
+                boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
+                        wakeup_sources ? WAKEUP_SOURCES_FORMAT :
+                                         PROC_WAKELOCKS_FORMAT,
+                        nameStringArray, wlData, null);
+
+                name = nameStringArray[0];
+                count = (int) wlData[1];
+
+                if (wakeup_sources) {
+                        // convert milliseconds to microseconds
+                        totalTime = wlData[2] * 1000;
+                } else {
+                        // convert nanoseconds to microseconds with rounding.
+                        totalTime = (wlData[2] + 500) / 1000;
+                }
+
+                if (parsed && name.length() > 0) {
+                    if (!staleStats.containsKey(name)) {
+                        staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
+                                sKernelWakelockUpdateVersion));
+                        numUpdatedWlNames++;
+                    } else {
+                        KernelWakelockStats.Entry kwlStats = staleStats.get(name);
+                        if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
+                            kwlStats.mCount += count;
+                            kwlStats.mTotalTime += totalTime;
+                        } else {
+                            kwlStats.mCount = count;
+                            kwlStats.mTotalTime = totalTime;
+                            kwlStats.mVersion = sKernelWakelockUpdateVersion;
+                            numUpdatedWlNames++;
+                        }
+                    }
+                }
+                startIndex = endIndex;
+            }
+
+            if (staleStats.size() != numUpdatedWlNames) {
+                // Don't report old data.
+                Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
+                while (itr.hasNext()) {
+                    if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+                        itr.remove();
+                    }
+                }
+            }
+
+            staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
+            return staleStats;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelWakelockStats.java b/core/java/com/android/internal/os/KernelWakelockStats.java
new file mode 100644
index 0000000..144ea00
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelWakelockStats.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.HashMap;
+
+/**
+ * Kernel wakelock stats object.
+ */
+public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> {
+    public static class Entry {
+        public int mCount;
+        public long mTotalTime;
+        public int mVersion;
+
+        Entry(int count, long totalTime, int version) {
+            mCount = count;
+            mTotalTime = totalTime;
+            mVersion = version;
+        }
+    }
+
+    int kernelWakelockVersion;
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3b779b7..b5e1de9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2188,7 +2188,7 @@
         systemDir.mkdirs();
         mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
-        mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+        mBatteryStatsService.scheduleWriteToDisk();
         mOnBattery = DEBUG_POWER ? true
                 : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
         mBatteryStatsService.getActiveStatistics().setCallback(this);
@@ -2432,7 +2432,7 @@
 
                 if (mLastWriteTime < (now-BATTERY_STATS_TIME)) {
                     mLastWriteTime = now;
-                    mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+                    mBatteryStatsService.scheduleWriteToDisk();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 80101f5..c8db3be 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -16,20 +16,26 @@
 
 package com.android.server.am;
 
+import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -38,10 +44,12 @@
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.PowerProfile;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 
 import java.io.File;
@@ -59,15 +67,52 @@
     static final String TAG = "BatteryStatsService";
 
     static IBatteryStats sService;
-    
     final BatteryStatsImpl mStats;
+    final BatteryStatsHandler mHandler;
     Context mContext;
     private boolean mBluetoothPendingStats;
     private BluetoothHeadset mBluetoothHeadset;
     PowerManagerInternal mPowerManagerInternal;
 
+    class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync {
+        public static final int MSG_SYNC_EXTERNAL_STATS = 1;
+        public static final int MSG_WRITE_TO_DISK = 2;
+
+        public BatteryStatsHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SYNC_EXTERNAL_STATS:
+                    updateExternalStats();
+                    break;
+
+                case MSG_WRITE_TO_DISK:
+                    updateExternalStats();
+                    synchronized (mStats) {
+                        mStats.writeAsyncLocked();
+                    }
+                    break;
+            }
+        }
+
+        @Override
+        public void scheduleSync() {
+            if (!hasMessages(MSG_SYNC_EXTERNAL_STATS)) {
+                sendEmptyMessage(MSG_SYNC_EXTERNAL_STATS);
+            }
+        }
+    }
+
     BatteryStatsService(File systemDir, Handler handler) {
-        mStats = new BatteryStatsImpl(systemDir, handler);
+        // Our handler here will be accessing the disk, use a different thread than
+        // what the ActivityManagerService gave us (no I/O on that one!).
+        mHandler = new BatteryStatsHandler(FgThread.getHandler().getLooper());
+
+        // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
+        mStats = new BatteryStatsImpl(systemDir, handler, mHandler);
     }
     
     public void publish(Context context) {
@@ -92,6 +137,8 @@
 
     public void shutdown() {
         Slog.w("BatteryStats", "Writing battery stats before shutdown...");
+
+        updateExternalStats();
         synchronized (mStats) {
             mStats.shutdownLocked();
         }
@@ -122,6 +169,14 @@
         return mStats;
     }
 
+    /**
+     * Schedules a write to disk to occur. This will cause the BatteryStatsImpl
+     * object to update with the latest info, then write to disk.
+     */
+    public void scheduleWriteToDisk() {
+        mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
+    }
+
     // These are for direct use by the activity manager...
 
     void addIsolatedUid(int isolatedUid, int appUid) {
@@ -174,7 +229,10 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        mStats.writeToParcel(out, 0);
+        updateExternalStats();
+        synchronized (mStats) {
+            mStats.writeToParcel(out, 0);
+        }
         byte[] data = out.marshall();
         out.recycle();
         return data;
@@ -186,7 +244,10 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        mStats.writeToParcel(out, 0);
+        updateExternalStats();
+        synchronized (mStats) {
+            mStats.writeToParcel(out, 0);
+        }
         byte[] data = out.marshall();
         out.recycle();
         try {
@@ -663,6 +724,7 @@
         }
     }
 
+    @Override
     public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -671,10 +733,10 @@
     }
 
     @Override
-    public void noteNetworkInterfaceType(String iface, int type) {
+    public void noteNetworkInterfaceType(String iface, int networkType) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteNetworkInterfaceTypeLocked(iface, type);
+            mStats.noteNetworkInterfaceTypeLocked(iface, networkType);
         }
     }
 
@@ -715,7 +777,22 @@
     public void setBatteryState(int status, int health, int plugType, int level,
             int temp, int volt) {
         enforceCallingPermission();
-        mStats.setBatteryState(status, health, plugType, level, temp, volt);
+        synchronized (mStats) {
+            final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+            if (mStats.isOnBattery() == onBattery) {
+                // The battery state has not changed, so we don't need to sync external
+                // stats immediately.
+                mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+                return;
+            }
+        }
+
+        // Sync external stats first as the battery has changed states. If we don't sync
+        // immediately here, we may not collect the relevant data later.
+        updateExternalStats();
+        synchronized (mStats) {
+            mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+        }
     }
     
     public long getAwakeTimeBattery() {
@@ -817,6 +894,7 @@
         return i;
     }
 
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -865,7 +943,9 @@
                         pw.println("Battery stats reset.");
                         noOutput = true;
                     }
+                    updateExternalStats();
                 } else if ("--write".equals(arg)) {
+                    updateExternalStats();
                     synchronized (mStats) {
                         mStats.writeSyncLocked();
                         pw.println("Battery stats written.");
@@ -934,6 +1014,10 @@
                 flags &= ~BatteryStats.DUMP_INCLUDE_HISTORY;
             }
         }
+
+        // Fetch data from external sources and update the BatteryStatsImpl object with them.
+        updateExternalStats();
+
         if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
             if (isRealCheckin) {
@@ -948,7 +1032,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        null, mStats.mHandler);
+                                        null, mStats.mHandler, null);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
@@ -978,4 +1062,85 @@
             }
         }
     }
+
+    // Objects for extracting data from external sources.
+    private final Object mExternalStatsLock = new Object();
+
+    @GuardedBy("mExternalStatsLock")
+    private IWifiManager mWifiManager;
+
+    // WiFi keeps an accumulated total of stats, unlike Bluetooth.
+    // Keep the last WiFi stats so we can compute a delta.
+    @GuardedBy("mExternalStatsLock")
+    private WifiActivityEnergyInfo mLastInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+
+    @GuardedBy("mExternalStatsLock")
+    private WifiActivityEnergyInfo pullWifiEnergyInfoLocked() {
+        if (mWifiManager == null) {
+            mWifiManager = IWifiManager.Stub.asInterface(
+                    ServiceManager.getService(Context.WIFI_SERVICE));
+            if (mWifiManager == null) {
+                return null;
+            }
+        }
+
+        try {
+            // We read the data even if we are not on battery. This is so that we keep the
+            // correct delta from when we should start reading (aka when we are on battery).
+            WifiActivityEnergyInfo info = mWifiManager.reportActivityInfo();
+            if (info != null && info.isValid()) {
+                // We will modify the last info object to be the delta, and store the new
+                // WifiActivityEnergyInfo object as our last one.
+                final WifiActivityEnergyInfo result = mLastInfo;
+                result.mTimestamp = info.getTimeStamp();
+                result.mStackState = info.getStackState();
+                result.mControllerTxTimeMs =
+                        info.getControllerTxTimeMillis()- mLastInfo.mControllerTxTimeMs;
+                result.mControllerRxTimeMs =
+                        info.getControllerRxTimeMillis() - mLastInfo.mControllerRxTimeMs;
+                result.mControllerIdleTimeMs =
+                        info.getControllerIdleTimeMillis() - mLastInfo.mControllerIdleTimeMs;
+                result.mControllerEnergyUsed =
+                        info.getControllerEnergyUsed() - mLastInfo.mControllerEnergyUsed;
+                mLastInfo = info;
+                return result;
+            }
+        } catch (RemoteException e) {
+            // Nothing to report, WiFi is dead.
+        }
+        return null;
+    }
+
+    @GuardedBy("mExternalStatsLock")
+    private BluetoothActivityEnergyInfo pullBluetoothEnergyInfoLocked() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
+                    BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
+            if (info != null && info.isValid()) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Fetches data from external sources (WiFi controller, bluetooth chipset) and updates
+     * batterystats with that information.
+     *
+     * We first grab a lock specific to this method, then once all the data has been collected,
+     * we grab the mStats lock and update the data.
+     */
+    void updateExternalStats() {
+        synchronized (mExternalStatsLock) {
+            final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked();
+            final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked();
+            synchronized (mStats) {
+                mStats.updateKernelWakelocksLocked();
+                mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime());
+                mStats.updateWifiStateLocked(wifiEnergyInfo);
+                mStats.updateBluetoothStateLocked(bluetoothEnergyInfo);
+            }
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
index 6263463..9284796 100644
--- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
+++ b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
@@ -26,12 +26,35 @@
  * @hide
  */
 public final class WifiActivityEnergyInfo implements Parcelable {
-    private final long mTimestamp;
-    private final int mStackState;
-    private final int mControllerTxTimeMs;
-    private final int mControllerRxTimeMs;
-    private final int mControllerIdleTimeMs;
-    private final int mControllerEnergyUsed;
+    /**
+     * @hide
+     */
+    public long mTimestamp;
+
+    /**
+     * @hide
+     */
+    public int mStackState;
+
+    /**
+     * @hide
+     */
+    public int mControllerTxTimeMs;
+
+    /**
+     * @hide
+     */
+    public int mControllerRxTimeMs;
+
+    /**
+     * @hide
+     */
+    public int mControllerIdleTimeMs;
+
+    /**
+     * @hide
+     */
+    public int mControllerEnergyUsed;
 
     public static final int STACK_STATE_INVALID = 0;
     public static final int STACK_STATE_STATE_ACTIVE = 1;