OSDN Git Service

BatteryStats: Cleanup external stats collection
authorAdam Lesinski <adamlesinski@google.com>
Fri, 26 May 2017 18:50:40 +0000 (11:50 -0700)
committerAdam Lesinski <adamlesinski@google.com>
Fri, 9 Jun 2017 22:23:04 +0000 (15:23 -0700)
Create a BatteryStatsWorker that internalizes the stats collection,
and returns a Future<?> so that uses-cases requiring synchronous
results can still wait on the async task.

Bug: 37645919
Bug: 38296815
Test: manual
Change-Id: I5b935e1877d9a17d2617f01478faa77e8a52a258

core/java/android/os/SynchronousResultReceiver.java
core/java/com/android/internal/os/BatteryStatsImpl.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/BatteryExternalStatsWorker.java [new file with mode: 0644]
services/core/java/com/android/server/am/BatteryStatsService.java

index d1b6288..6e1d4b3 100644 (file)
@@ -43,9 +43,19 @@ public class SynchronousResultReceiver extends ResultReceiver {
     }
 
     private final CompletableFuture<Result> mFuture = new CompletableFuture<>();
+    private final String mName;
 
     public SynchronousResultReceiver() {
         super((Handler) null);
+        mName = null;
+    }
+
+    /**
+     * @param name Name for logging purposes
+     */
+    public SynchronousResultReceiver(String name) {
+        super((Handler) null);
+        mName = name;
     }
 
     @Override
@@ -54,6 +64,10 @@ public class SynchronousResultReceiver extends ResultReceiver {
         mFuture.complete(new Result(resultCode, resultData));
     }
 
+    public String getName() {
+        return mName;
+    }
+
     /**
      * Blocks waiting for the result from the remote client.
      *
index 2363f68..7ab0ba1 100644 (file)
@@ -95,6 +95,7 @@ import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -236,12 +237,12 @@ public class BatteryStatsImpl extends BatteryStats {
         int UPDATE_BT = 0x08;
         int UPDATE_ALL = UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
 
-        void scheduleSync(String reason, int flags);
-        void scheduleCpuSyncDueToRemovedUid(int uid);
+        Future<?> scheduleSync(String reason, int flags);
+        Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
     }
 
     public final MyHandler mHandler;
-    private final ExternalStatsSync mExternalSync;
+    private ExternalStatsSync mExternalSync = null;
 
     private BatteryCallback mCallback;
 
@@ -630,7 +631,6 @@ public class BatteryStatsImpl extends BatteryStats {
         mCheckinFile = null;
         mDailyFile = null;
         mHandler = null;
-        mExternalSync = null;
         mPlatformIdleStateCallback = null;
         clearHistoryLocked();
     }
@@ -8597,17 +8597,16 @@ public class BatteryStatsImpl extends BatteryStats {
         return mCpuFreqs;
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync) {
-        this(new SystemClocks(), systemDir, handler, externalSync, null);
+    public BatteryStatsImpl(File systemDir, Handler handler) {
+        this(new SystemClocks(), systemDir, handler, null);
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync,
-                            PlatformIdleStateCallback cb) {
-        this(new SystemClocks(), systemDir, handler, externalSync, cb);
+    public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb) {
+        this(new SystemClocks(), systemDir, handler, cb);
     }
 
     public BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
-            ExternalStatsSync externalSync, PlatformIdleStateCallback cb) {
+            PlatformIdleStateCallback cb) {
         init(clocks);
 
         if (systemDir != null) {
@@ -8618,7 +8617,6 @@ public class BatteryStatsImpl extends BatteryStats {
         }
         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(mClocks, null, -1, null, mOnBatteryTimeBase);
@@ -8745,6 +8743,10 @@ public class BatteryStatsImpl extends BatteryStats {
         }
     }
 
+    public void setExternalStatsSyncLocked(ExternalStatsSync sync) {
+        mExternalSync = sync;
+    }
+
     public void updateDailyDeadlineLocked() {
         // Get the current time.
         long currentTime = mDailyStartTime = System.currentTimeMillis();
@@ -9750,7 +9752,7 @@ public class BatteryStatsImpl extends BatteryStats {
                 return;
             }
 
-            final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+            final long elapsedRealtimeMs = mClocks.elapsedRealtime();
             long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000);
             mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
index f80dd62..e65355d 100644 (file)
@@ -2734,7 +2734,7 @@ public class ActivityManagerService extends IActivityManager.Stub
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         systemDir.mkdirs();
-        mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
+        mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.scheduleWriteToDisk();
         mOnBattery = DEBUG_POWER ? true
@@ -2837,7 +2837,7 @@ public class ActivityManagerService extends IActivityManager.Stub
         removeAllProcessGroups();
         mProcessCpuThread.start();
 
-        mBatteryStatsService.publish(mContext);
+        mBatteryStatsService.publish();
         mAppOpsService.publish(mContext);
         Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, new LocalService());
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
new file mode 100644 (file)
index 0000000..eb84adc
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2017 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.server.am;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.BatteryStats;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BatteryStatsImpl;
+
+import libcore.util.EmptyArray;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
+ * dedicated thread and updates BatteryStatsImpl with that information.
+ *
+ * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
+ * readily available data is pushed into BatteryStatsImpl with the lock held.
+ */
+class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
+    private static final String TAG = "BatteryExternalStatsWorker";
+    private static final boolean DEBUG = false;
+
+    /**
+     * How long to wait on an individual subsystem to return its stats.
+     */
+    private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
+    // There is some accuracy error in wifi reports so allow some slop in the results.
+    private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
+
+    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(
+            (ThreadFactory) r -> {
+                Thread t = new Thread(r, "batterystats-worker");
+                t.setPriority(Thread.NORM_PRIORITY);
+                return t;
+            });
+
+    private final Context mContext;
+    private final BatteryStatsImpl mStats;
+
+    @GuardedBy("this")
+    private int mUpdateFlags = 0;
+
+    @GuardedBy("this")
+    private Future<?> mCurrentFuture = null;
+
+    @GuardedBy("this")
+    private String mCurrentReason = null;
+
+    @GuardedBy("this")
+    private final IntArray mUidsToRemove = new IntArray();
+
+    private final Object mWorkerLock = new Object();
+
+    @GuardedBy("mWorkerLock")
+    private IWifiManager mWifiManager = null;
+
+    @GuardedBy("mWorkerLock")
+    private TelephonyManager mTelephony = null;
+
+    // WiFi keeps an accumulated total of stats, unlike Bluetooth.
+    // Keep the last WiFi stats so we can compute a delta.
+    @GuardedBy("mWorkerLock")
+    private WifiActivityEnergyInfo mLastInfo =
+            new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0);
+
+    BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
+        mContext = context;
+        mStats = stats;
+    }
+
+    @Override
+    public synchronized Future<?> scheduleSync(String reason, int flags) {
+        return scheduleSyncLocked(reason, flags);
+    }
+
+    @Override
+    public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+        mUidsToRemove.add(uid);
+        return scheduleSyncLocked("remove-uid", UPDATE_CPU);
+    }
+
+    public synchronized Future<?> scheduleWrite() {
+        scheduleSyncLocked("write", UPDATE_ALL);
+        // Since we use a single threaded executor, we can assume the next scheduled task's
+        // Future finishes after the sync.
+        return mExecutorService.submit(mWriteTask);
+    }
+
+    /**
+     * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
+     * within the task, never wait on the resulting Future. This will result in a deadlock.
+     */
+    public synchronized void scheduleRunnable(Runnable runnable) {
+        mExecutorService.submit(runnable);
+    }
+
+    public void shutdown() {
+        mExecutorService.shutdownNow();
+    }
+
+    private Future<?> scheduleSyncLocked(String reason, int flags) {
+        if (mCurrentFuture == null) {
+            mUpdateFlags = flags;
+            mCurrentReason = reason;
+            mCurrentFuture = mExecutorService.submit(mSyncTask);
+        }
+        mUpdateFlags |= flags;
+        return mCurrentFuture;
+    }
+
+    private final Runnable mSyncTask = new Runnable() {
+        @Override
+        public void run() {
+            // Capture a snapshot of the state we are meant to process.
+            final int updateFlags;
+            final String reason;
+            final int[] uidsToRemove;
+            synchronized (BatteryExternalStatsWorker.this) {
+                updateFlags = mUpdateFlags;
+                reason = mCurrentReason;
+                uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
+                mUpdateFlags = 0;
+                mCurrentReason = null;
+                mUidsToRemove.clear();
+                mCurrentFuture = null;
+            }
+
+            synchronized (mWorkerLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
+                }
+                try {
+                    updateExternalStatsLocked(reason, updateFlags);
+                } finally {
+                    if (DEBUG) {
+                        Slog.d(TAG, "end updateExternalStatsSync");
+                    }
+                }
+            }
+
+            // Clean up any UIDs if necessary.
+            synchronized (mStats) {
+                for (int uid : uidsToRemove) {
+                    mStats.removeIsolatedUidLocked(uid);
+                }
+            }
+        }
+    };
+
+    private final Runnable mWriteTask = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mStats) {
+                mStats.writeAsyncLocked();
+            }
+        }
+    };
+
+    private void updateExternalStatsLocked(final String reason, int updateFlags) {
+        // We will request data from external processes asynchronously, and wait on a timeout.
+        SynchronousResultReceiver wifiReceiver = null;
+        SynchronousResultReceiver bluetoothReceiver = null;
+        SynchronousResultReceiver modemReceiver = null;
+
+        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
+            // We were asked to fetch WiFi data.
+            if (mWifiManager == null) {
+                mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
+                        Context.WIFI_SERVICE));
+            }
+
+            if (mWifiManager != null) {
+                try {
+                    wifiReceiver = new SynchronousResultReceiver("wifi");
+                    mWifiManager.requestActivityInfo(wifiReceiver);
+                } catch (RemoteException e) {
+                    // Oh well.
+                }
+            }
+        }
+
+        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
+            // We were asked to fetch Bluetooth data.
+            final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter != null) {
+                bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
+                adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+            }
+        }
+
+        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
+            // We were asked to fetch Telephony data.
+            if (mTelephony == null) {
+                mTelephony = TelephonyManager.from(mContext);
+            }
+
+            if (mTelephony != null) {
+                modemReceiver = new SynchronousResultReceiver("telephony");
+                mTelephony.requestModemActivityInfo(modemReceiver);
+            }
+        }
+
+        final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+        final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
+        final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+
+        synchronized (mStats) {
+            mStats.addHistoryEventLocked(
+                    SystemClock.elapsedRealtime(),
+                    SystemClock.uptimeMillis(),
+                    BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
+                    reason, 0);
+
+            if ((updateFlags & UPDATE_CPU) != 0) {
+                mStats.updateCpuTimeLocked(true /* updateCpuFreqData */);
+                mStats.updateKernelWakelocksLocked();
+                mStats.updateKernelMemoryBandwidthLocked();
+            }
+
+            if (bluetoothInfo != null) {
+                if (bluetoothInfo.isValid()) {
+                    mStats.updateBluetoothStateLocked(bluetoothInfo);
+                } else {
+                    Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
+                }
+            }
+        }
+
+        // WiFi and Modem state are updated without the mStats lock held, because they
+        // do some network stats retrieval before internally grabbing the mStats lock.
+
+        if (wifiInfo != null) {
+            if (wifiInfo.isValid()) {
+                mStats.updateWifiState(extractDeltaLocked(wifiInfo));
+            } else {
+                Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
+            }
+        }
+
+        if (modemInfo != null) {
+            if (modemInfo.isValid()) {
+                mStats.updateMobileRadioState(modemInfo);
+            } else {
+                Slog.e(TAG, "modem info is invalid: " + modemInfo);
+            }
+        }
+    }
+
+    /**
+     * Helper method to extract the Parcelable controller info from a
+     * SynchronousResultReceiver.
+     */
+    private static <T extends Parcelable> T awaitControllerInfo(
+            @Nullable SynchronousResultReceiver receiver) {
+        if (receiver == null) {
+            return null;
+        }
+
+        try {
+            final SynchronousResultReceiver.Result result =
+                    receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
+            if (result.bundle != null) {
+                // This is the final destination for the Bundle.
+                result.bundle.setDefusable(true);
+
+                final T data = result.bundle.getParcelable(
+                        BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
+                if (data != null) {
+                    return data;
+                }
+            }
+            Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
+        } catch (TimeoutException e) {
+            Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
+        }
+        return null;
+    }
+
+    private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+        final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
+        final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
+        final long lastTxMs = mLastInfo.mControllerTxTimeMs;
+        final long lastRxMs = mLastInfo.mControllerRxTimeMs;
+        final long lastEnergy = mLastInfo.mControllerEnergyUsed;
+
+        // We will modify the last info object to be the delta, and store the new
+        // WifiActivityEnergyInfo object as our last one.
+        final WifiActivityEnergyInfo delta = mLastInfo;
+        delta.mTimestamp = latest.getTimeStamp();
+        delta.mStackState = latest.getStackState();
+
+        final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
+        final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
+        final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
+
+        if (txTimeMs < 0 || rxTimeMs < 0) {
+            // The stats were reset by the WiFi system (which is why our delta is negative).
+            // Returns the unaltered stats.
+            delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
+            delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
+            delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
+            delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
+            Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
+        } else {
+            final long totalActiveTimeMs = txTimeMs + rxTimeMs;
+            long maxExpectedIdleTimeMs;
+            if (totalActiveTimeMs > timePeriodMs) {
+                // Cap the max idle time at zero since the active time consumed the whole time
+                maxExpectedIdleTimeMs = 0;
+                if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append("Total Active time ");
+                    TimeUtils.formatDuration(totalActiveTimeMs, sb);
+                    sb.append(" is longer than sample period ");
+                    TimeUtils.formatDuration(timePeriodMs, sb);
+                    sb.append(".\n");
+                    sb.append("Previous WiFi snapshot: ").append("idle=");
+                    TimeUtils.formatDuration(lastIdleMs, sb);
+                    sb.append(" rx=");
+                    TimeUtils.formatDuration(lastRxMs, sb);
+                    sb.append(" tx=");
+                    TimeUtils.formatDuration(lastTxMs, sb);
+                    sb.append(" e=").append(lastEnergy);
+                    sb.append("\n");
+                    sb.append("Current WiFi snapshot: ").append("idle=");
+                    TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
+                    sb.append(" rx=");
+                    TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
+                    sb.append(" tx=");
+                    TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
+                    sb.append(" e=").append(latest.mControllerEnergyUsed);
+                    Slog.wtf(TAG, sb.toString());
+                }
+            } else {
+                maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
+            }
+            // These times seem to be the most reliable.
+            delta.mControllerTxTimeMs = txTimeMs;
+            delta.mControllerRxTimeMs = rxTimeMs;
+            // 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 ensure that it is less than the expected idle
+            // time from the difference in timestamps.
+            // b/21613534
+            delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
+            delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
+        }
+
+        mLastInfo = latest;
+        return delta;
+    }
+}
index 06ab75a..cf322d7 100644 (file)
 
 package com.android.server.am;
 
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI;
-
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
-import android.bluetooth.BluetoothAdapter;
 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.PowerSaveState;
 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.ParcelFormatException;
-import android.os.Parcelable;
 import android.os.PowerManagerInternal;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SynchronousResultReceiver;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -54,18 +42,14 @@ import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
-import android.util.IntArray;
 import android.util.Slog;
-import android.util.TimeUtils;
 
-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.internal.util.DumpUtils;
 import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
 import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import java.io.File;
@@ -79,7 +63,8 @@ import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 /**
  * All information we are collecting about things that can happen that impact
@@ -91,101 +76,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub
     static final String TAG = "BatteryStatsService";
     static final boolean DBG = false;
 
-    /**
-     * How long to wait on an individual subsystem to return its stats.
-     */
-    private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
-
-    // There is some accuracy error in wifi reports so allow some slop in the results.
-    private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
-
     private static IBatteryStats sService;
 
     final BatteryStatsImpl mStats;
-    private final BatteryStatsHandler mHandler;
-    private Context mContext;
-    private IWifiManager mWifiManager;
-    private TelephonyManager mTelephony;
-
-    // Lock acquired when extracting data from external sources.
-    private final Object mExternalStatsLock = new Object();
-
-    // 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, new long[]{0}, 0, 0, 0);
-
-    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;
-
-        private int mUpdateFlags = 0;
-        private IntArray mUidsToRemove = new IntArray();
-
-        public BatteryStatsHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SYNC_EXTERNAL_STATS:
-                    final int updateFlags;
-                    synchronized (this) {
-                        removeMessages(MSG_SYNC_EXTERNAL_STATS);
-                        updateFlags = mUpdateFlags;
-                        mUpdateFlags = 0;
-                    }
-                    updateExternalStatsSync((String)msg.obj, updateFlags);
-
-                    // other parts of the system could be calling into us
-                    // from mStats in order to report of changes. We must grab the mStats
-                    // lock before grabbing our own or we'll end up in a deadlock.
-                    synchronized (mStats) {
-                        synchronized (this) {
-                            final int numUidsToRemove = mUidsToRemove.size();
-                            for (int i = 0; i < numUidsToRemove; i++) {
-                                mStats.removeIsolatedUidLocked(mUidsToRemove.get(i));
-                            }
-                        }
-                        mUidsToRemove.clear();
-                    }
-                    break;
-
-                case MSG_WRITE_TO_DISK:
-                    updateExternalStatsSync("write", UPDATE_ALL);
-                    if (DBG) Slog.d(TAG, "begin writeAsyncLocked");
-                    synchronized (mStats) {
-                        mStats.writeAsyncLocked();
-                    }
-                    if (DBG) Slog.d(TAG, "end writeAsyncLocked");
-                    break;
-            }
-        }
-
-        @Override
-        public void scheduleSync(String reason, int updateFlags) {
-            synchronized (this) {
-                scheduleSyncLocked(reason, updateFlags);
-            }
-        }
-
-        @Override
-        public void scheduleCpuSyncDueToRemovedUid(int uid) {
-            synchronized (this) {
-                scheduleSyncLocked("remove-uid", UPDATE_CPU);
-                mUidsToRemove.add(uid);
-            }
-        }
-
-        private void scheduleSyncLocked(String reason, int updateFlags) {
-            if (mUpdateFlags == 0) {
-                sendMessage(Message.obtain(this, MSG_SYNC_EXTERNAL_STATS, reason));
-            }
-            mUpdateFlags |= updateFlags;
-        }
-    }
+    private final Context mContext;
+    private final BatteryExternalStatsWorker mWorker;
 
     private native int getPlatformLowPowerStats(ByteBuffer outBuffer);
     private native int getSubsystemLowPowerStats(ByteBuffer outBuffer);
@@ -242,29 +137,34 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         }
     }
 
-    BatteryStatsService(File systemDir, Handler 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!).
-        final ServiceThread thread = new ServiceThread("batterystats-sync",
-                Process.THREAD_PRIORITY_DEFAULT, true);
-        thread.start();
-        mHandler = new BatteryStatsHandler(thread.getLooper());
-
+    BatteryStatsService(Context context, File systemDir, Handler handler) {
         // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
-        mStats = new BatteryStatsImpl(systemDir, handler, mHandler, this);
+        mContext = context;
+        mStats = new BatteryStatsImpl(systemDir, handler, this);
+        mWorker = new BatteryExternalStatsWorker(context, mStats);
+        mStats.setExternalStatsSyncLocked(mWorker);
+        mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
+        mStats.setPowerProfileLocked(new PowerProfile(context));
     }
 
-    public void publish(Context context) {
-        mContext = context;
-        synchronized (mStats) {
-            mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_radioScanningTimeout)
-                    * 1000L);
-            mStats.setPowerProfileLocked(new PowerProfile(context));
-        }
+    public void publish() {
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
+    private static void awaitUninterruptibly(Future<?> future) {
+        while (true) {
+            try {
+                future.get();
+                return;
+            } catch (ExecutionException e) {
+                return;
+            } catch (InterruptedException e) {
+                // Keep looping
+            }
+        }
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize the low power observer later.
@@ -283,13 +183,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
     public void shutdown() {
         Slog.w("BatteryStats", "Writing battery stats before shutdown...");
 
-        updateExternalStatsSync("shutdown", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+        awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+
         synchronized (mStats) {
             mStats.shutdownLocked();
         }
 
         // Shutdown the thread we made.
-        mHandler.getLooper().quit();
+        mWorker.shutdown();
     }
     
     public static IBatteryStats getService() {
@@ -327,7 +228,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
      * object to update with the latest info, then write to disk.
      */
     public void scheduleWriteToDisk() {
-        mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
+        mWorker.scheduleWrite();
     }
 
     // These are for direct use by the activity manager...
@@ -391,7 +292,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -406,7 +307,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -635,13 +536,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub
 
     public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) {
         enforceCallingPermission();
-        boolean update;
+        final boolean update;
         synchronized (mStats) {
             update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid);
         }
 
         if (update) {
-            mHandler.scheduleSync("modem-data", UPDATE_RADIO);
+            mWorker.scheduleSync("modem-data", BatteryExternalStatsWorker.UPDATE_RADIO);
         }
     }
 
@@ -792,8 +693,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
                 final String type = (powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH ||
                         powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM) ? "active"
                         : "inactive";
-                mHandler.scheduleSync("wifi-data: " + type,
-                        UPDATE_WIFI);
+                mWorker.scheduleSync("wifi-data: " + type, BatteryExternalStatsWorker.UPDATE_WIFI);
             }
             mStats.noteWifiRadioPowerState(powerState, tsNanos, uid);
         }
@@ -951,7 +851,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub
     @Override
     public void noteNetworkStatsEnabled() {
         enforceCallingPermission();
-        mHandler.scheduleSync("network-stats-enabled", UPDATE_RADIO | UPDATE_WIFI);
+        // 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.
+        mWorker.scheduleSync("network-stats-enabled",
+                BatteryExternalStatsWorker.UPDATE_RADIO | BatteryExternalStatsWorker.UPDATE_WIFI);
     }
 
     @Override
@@ -1028,9 +932,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
             return;
         }
 
-        synchronized (mStats) {
-            mStats.updateBluetoothStateLocked(info);
-        }
+        mStats.updateBluetoothStateLocked(info);
     }
 
     @Override
@@ -1057,28 +959,29 @@ public final class BatteryStatsService extends IBatteryStats.Stub
 
         // BatteryService calls us here and we may update external state. It would be wrong
         // to block such a low level service like BatteryService on external stats like WiFi.
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                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,
-                                chargeUAh, chargeFullUAh);
-                        return;
-                    }
+        mWorker.scheduleRunnable(() -> {
+            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,
+                            chargeUAh, chargeFullUAh);
+                    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.
-                updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+            // Sync external stats first as the battery has changed states. If we don't sync
+            // before changing the state, we may not collect the relevant data later.
+            // Order here is guaranteed since we're scheduling from the same thread and we are
+            // using a single threaded executor.
+            mWorker.scheduleSync("battery-state", BatteryExternalStatsWorker.UPDATE_ALL);
+            mWorker.scheduleRunnable(() -> {
                 synchronized (mStats) {
                     mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
                             chargeUAh, chargeFullUAh);
                 }
-            }
+            });
         });
     }
     
@@ -1260,9 +1163,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
                         pw.println("Battery stats reset.");
                         noOutput = true;
                     }
-                    updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+                    mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
                 } else if ("--write".equals(arg)) {
-                    updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+                    awaitUninterruptibly(mWorker.scheduleSync("dump",
+                            BatteryExternalStatsWorker.UPDATE_ALL));
                     synchronized (mStats) {
                         mStats.writeSyncLocked();
                         pw.println("Battery stats written.");
@@ -1326,7 +1230,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
                 flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
             }
             // Fetch data from external sources and update the BatteryStatsImpl object with them.
-            updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+            awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1391,229 +1295,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         }
     }
 
-    private WifiActivityEnergyInfo extractDelta(WifiActivityEnergyInfo latest) {
-        final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
-        final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
-        final long lastTxMs = mLastInfo.mControllerTxTimeMs;
-        final long lastRxMs = mLastInfo.mControllerRxTimeMs;
-        final long lastEnergy = mLastInfo.mControllerEnergyUsed;
-
-        // We will modify the last info object to be the delta, and store the new
-        // WifiActivityEnergyInfo object as our last one.
-        final WifiActivityEnergyInfo delta = mLastInfo;
-        delta.mTimestamp = latest.getTimeStamp();
-        delta.mStackState = latest.getStackState();
-
-        final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
-        final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
-        final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
-
-        if (txTimeMs < 0 || rxTimeMs < 0) {
-            // The stats were reset by the WiFi system (which is why our delta is negative).
-            // Returns the unaltered stats.
-            delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
-            delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
-            delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
-            delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
-            Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
-        } else {
-            final long totalActiveTimeMs = txTimeMs + rxTimeMs;
-            long maxExpectedIdleTimeMs;
-            if (totalActiveTimeMs > timePeriodMs) {
-                // Cap the max idle time at zero since the active time consumed the whole time
-                maxExpectedIdleTimeMs = 0;
-                if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
-                    StringBuilder sb = new StringBuilder();
-                    sb.append("Total Active time ");
-                    TimeUtils.formatDuration(totalActiveTimeMs, sb);
-                    sb.append(" is longer than sample period ");
-                    TimeUtils.formatDuration(timePeriodMs, sb);
-                    sb.append(".\n");
-                    sb.append("Previous WiFi snapshot: ").append("idle=");
-                    TimeUtils.formatDuration(lastIdleMs, sb);
-                    sb.append(" rx=");
-                    TimeUtils.formatDuration(lastRxMs, sb);
-                    sb.append(" tx=");
-                    TimeUtils.formatDuration(lastTxMs, sb);
-                    sb.append(" e=").append(lastEnergy);
-                    sb.append("\n");
-                    sb.append("Current WiFi snapshot: ").append("idle=");
-                    TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
-                    sb.append(" rx=");
-                    TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
-                    sb.append(" tx=");
-                    TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
-                    sb.append(" e=").append(latest.mControllerEnergyUsed);
-                    Slog.wtf(TAG, sb.toString());
-                }
-            } else {
-                maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
-            }
-            // These times seem to be the most reliable.
-            delta.mControllerTxTimeMs = txTimeMs;
-            delta.mControllerRxTimeMs = rxTimeMs;
-            // 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 ensure that it is less than the expected idle
-            // time from the difference in timestamps.
-            // b/21613534
-            delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
-            delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
-        }
-
-        mLastInfo = latest;
-        return delta;
-    }
-
-    /**
-     * Helper method to extract the Parcelable controller info from a
-     * SynchronousResultReceiver.
-     */
-    private static <T extends Parcelable> T awaitControllerInfo(
-            @Nullable SynchronousResultReceiver receiver) throws TimeoutException {
-        if (receiver == null) {
-            return null;
-        }
-
-        final SynchronousResultReceiver.Result result =
-                receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
-        if (result.bundle != null) {
-            // This is the final destination for the Bundle.
-            result.bundle.setDefusable(true);
-
-            final T data = result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
-            if (data != null) {
-                return data;
-            }
-        }
-        Slog.e(TAG, "no controller energy info supplied");
-        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.
-     *
-     * @param reason The reason why this collection was requested. Useful for debugging.
-     * @param updateFlags Which external stats to update. Can be a combination of
-     *                    {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_CPU},
-     *                    {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_RADIO},
-     *                    {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_WIFI},
-     *                    and {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_BT}.
-     */
-    void updateExternalStatsSync(final String reason, int updateFlags) {
-        SynchronousResultReceiver wifiReceiver = null;
-        SynchronousResultReceiver bluetoothReceiver = null;
-        SynchronousResultReceiver modemReceiver = null;
-
-        if (DBG) Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
-        synchronized (mExternalStatsLock) {
-            if (mContext == null) {
-                // Don't do any work yet.
-                if (DBG) Slog.d(TAG, "end updateExternalStatsSync");
-                return;
-            }
-
-            if ((updateFlags & UPDATE_WIFI) != 0) {
-                if (mWifiManager == null) {
-                    mWifiManager = IWifiManager.Stub.asInterface(
-                            ServiceManager.getService(Context.WIFI_SERVICE));
-                }
-
-                if (mWifiManager != null) {
-                    try {
-                        wifiReceiver = new SynchronousResultReceiver();
-                        mWifiManager.requestActivityInfo(wifiReceiver);
-                    } catch (RemoteException e) {
-                        // Oh well.
-                    }
-                }
-            }
-
-            if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
-                final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-                if (adapter != null) {
-                    bluetoothReceiver = new SynchronousResultReceiver();
-                    adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
-                }
-            }
-
-            if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
-                if (mTelephony == null) {
-                    mTelephony = TelephonyManager.from(mContext);
-                }
-
-                if (mTelephony != null) {
-                    modemReceiver = new SynchronousResultReceiver();
-                    mTelephony.requestModemActivityInfo(modemReceiver);
-                }
-            }
-
-            WifiActivityEnergyInfo wifiInfo = null;
-            BluetoothActivityEnergyInfo bluetoothInfo = null;
-            ModemActivityInfo modemInfo = null;
-            try {
-                wifiInfo = awaitControllerInfo(wifiReceiver);
-            } catch (TimeoutException e) {
-                Slog.w(TAG, "Timeout reading wifi stats");
-            }
-
-            try {
-                bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
-            } catch (TimeoutException e) {
-                Slog.w(TAG, "Timeout reading bt stats");
-            }
-
-            try {
-                modemInfo = awaitControllerInfo(modemReceiver);
-            } catch (TimeoutException e) {
-                Slog.w(TAG, "Timeout reading modem stats");
-            }
-
-            synchronized (mStats) {
-                mStats.addHistoryEventLocked(
-                        SystemClock.elapsedRealtime(),
-                        SystemClock.uptimeMillis(),
-                        BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
-                        reason, 0);
-
-                if ((updateFlags & UPDATE_CPU) != 0) {
-                    mStats.updateCpuTimeLocked(true /* updateCpuFreqData */);
-                }
-                mStats.updateKernelWakelocksLocked();
-                mStats.updateKernelMemoryBandwidthLocked();
-
-                if (bluetoothInfo != null) {
-                    if (bluetoothInfo.isValid()) {
-                        mStats.updateBluetoothStateLocked(bluetoothInfo);
-                    } else {
-                        Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
-                    }
-                }
-            }
-
-            if (wifiInfo != null) {
-                if (wifiInfo.isValid()) {
-                    mStats.updateWifiState(extractDelta(wifiInfo));
-                } else {
-                    Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
-                }
-            }
-
-            if (modemInfo != null) {
-                if (modemInfo.isValid()) {
-                    mStats.updateMobileRadioState(modemInfo);
-                } else {
-                    Slog.e(TAG, "modem info is invalid: " + modemInfo);
-                }
-            }
-        }
-        if (DBG) Slog.d(TAG, "end updateExternalStatsSync");
-    }
-
     /**
      * Gets a snapshot of the system health for a particular uid.
      */
@@ -1625,8 +1306,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         }
         long ident = Binder.clearCallingIdentity();
         try {
-            updateExternalStatsSync("get-health-stats-for-uid",
-                    BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
+                    BatteryExternalStatsWorker.UPDATE_ALL));
             synchronized (mStats) {
                 return getHealthStatsForUidLocked(requestUid);
             }
@@ -1650,8 +1331,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
         long ident = Binder.clearCallingIdentity();
         int i=-1;
         try {
-            updateExternalStatsSync("get-health-stats-for-uids",
-                    BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
+                    BatteryExternalStatsWorker.UPDATE_ALL));
             synchronized (mStats) {
                 final int N = requestUids.length;
                 final HealthStatsParceler[] results = new HealthStatsParceler[N];