Merge changes I6b2f5e01,Ice1b2a7b,Ib75f0162,I70b005a4,I8314a32b

* changes:
  Migrate pullBluetoothActivityInfo
  Migrate pullBluetoothBytesTransfer
  Migrate pullMobileBytesTransferBackground
  Migrate pullMobileBytesTransfer
  Migrate pullWifiBytesTransferBackground
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 0f981e2..b3d41988f 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -714,60 +714,6 @@
         }
     }
 
-    private void addNetworkStats(
-            int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
-        int size = stats.size();
-        long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
-        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
-        for (int j = 0; j < size; j++) {
-            stats.getValues(j, entry);
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos);
-            e.writeInt(entry.uid);
-            if (withFGBG) {
-                e.writeInt(entry.set);
-            }
-            e.writeLong(entry.rxBytes);
-            e.writeLong(entry.rxPackets);
-            e.writeLong(entry.txBytes);
-            e.writeLong(entry.txPackets);
-            ret.add(e);
-        }
-    }
-
-    /**
-     * Allows rollups per UID but keeping the set (foreground/background) slicing.
-     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
-     */
-    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
-        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        entry.iface = NetworkStats.IFACE_ALL;
-        entry.tag = NetworkStats.TAG_NONE;
-        entry.metered = NetworkStats.METERED_ALL;
-        entry.roaming = NetworkStats.ROAMING_ALL;
-
-        int size = stats.size();
-        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
-        for (int i = 0; i < size; i++) {
-            stats.getValues(i, recycle);
-
-            // Skip specific tags, since already counted in TAG_NONE
-            if (recycle.tag != NetworkStats.TAG_NONE) continue;
-
-            entry.set = recycle.set; // Allows slicing by background/foreground
-            entry.uid = recycle.uid;
-            entry.rxBytes = recycle.rxBytes;
-            entry.rxPackets = recycle.rxPackets;
-            entry.txBytes = recycle.txBytes;
-            entry.txPackets = recycle.txPackets;
-            // Operations purposefully omitted since we don't use them for statsd.
-            ret.combineValues(entry);
-        }
-        return ret;
-    }
-
     /**
      * Helper method to extract the Parcelable controller info from a
      * SynchronousResultReceiver.
@@ -815,94 +761,6 @@
         }
     }
 
-    private void pullWifiBytesTransferByFgBg(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getWifiIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            NetworkStats stats = rollupNetworkStatsByFGBG(
-                    mNetworkStatsService.getDetailedUidStats(ifaces));
-            addNetworkStats(tagId, pulledData, stats, true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullMobileBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getMobileIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            // Combine all the metrics per Uid into one record.
-            NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
-            addNetworkStats(tagId, pulledData, stats, false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullBluetoothBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BluetoothActivityEnergyInfo info = fetchBluetoothData();
-        if (info.getUidTraffic() != null) {
-            for (UidTraffic traffic : info.getUidTraffic()) {
-                StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                        wallClockNanos);
-                e.writeInt(traffic.getUid());
-                e.writeLong(traffic.getRxBytes());
-                e.writeLong(traffic.getTxBytes());
-                pulledData.add(e);
-            }
-        }
-    }
-
-    private void pullMobileBytesTransferByFgBg(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getMobileIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            NetworkStats stats = rollupNetworkStatsByFGBG(
-                    mNetworkStatsService.getDetailedUidStats(ifaces));
-            addNetworkStats(tagId, pulledData, stats, true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private void pullCpuTimePerFreq(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -1052,33 +910,6 @@
         }
     }
 
-    private void pullBluetoothActivityInfo(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BluetoothActivityEnergyInfo info = fetchBluetoothData();
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(info.getTimeStamp());
-        e.writeInt(info.getBluetoothStackState());
-        e.writeLong(info.getControllerTxTimeMillis());
-        e.writeLong(info.getControllerRxTimeMillis());
-        e.writeLong(info.getControllerIdleTimeMillis());
-        e.writeLong(info.getControllerEnergyUsed());
-        pulledData.add(e);
-    }
-
-    private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
-        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
-                    "bluetooth");
-            adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
-            return awaitControllerInfo(bluetoothReceiver);
-        } else {
-            Slog.e(TAG, "Failed to get bluetooth adapter!");
-            return null;
-        }
-    }
-
     private void pullSystemElapsedRealtime(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2291,26 +2122,6 @@
         long elapsedNanos = SystemClock.elapsedRealtimeNanos();
         long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
         switch (tagId) {
-            
-            case StatsLog.MOBILE_BYTES_TRANSFER: {
-                pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
-                pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
-                pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
-            case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
-                pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
 
             case StatsLog.KERNEL_WAKELOCK: {
                 pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
@@ -2352,11 +2163,6 @@
                 break;
             }
 
-            case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
-                pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-
             case StatsLog.SYSTEM_UPTIME: {
                 pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index b568033..06046c1 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -60,27 +60,6 @@
 
 std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
 
-        // wifi_bytes_transfer_by_fg_bg
-        {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
-         {.additiveFields = {3, 4, 5, 6},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
-
-        // mobile_bytes_transfer
-        {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
-
-        // mobile_bytes_transfer_by_fg_bg
-        {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
-         {.additiveFields = {3, 4, 5, 6},
-          .puller =
-                  new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
-
-        // bluetooth_bytes_transfer
-        {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
-
         // kernel_wakelock
         {{.atomTag = android::util::KERNEL_WAKELOCK},
          {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
@@ -132,10 +111,6 @@
         {{.atomTag = android::util::MODEM_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
 
-        // bluetooth_activity_info
-        {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
-
         // system_elapsed_realtime
         {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
          {.coolDownNs = NS_PER_SEC,
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index a75f1c2..bb910b2 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -186,6 +186,12 @@
     private static final String TAG = "StatsPullAtomService";
     private static final boolean DEBUG = true;
 
+    private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
+    /**
+     * How long to wait on an individual subsystem to return its stats.
+     */
+    private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
     private final Object mNetworkStatsLock = new Object();
     @GuardedBy("mNetworkStatsLock")
     private INetworkStatsService mNetworkStatsService;
@@ -327,11 +333,12 @@
     }
     private void registerWifiBytesTransfer() {
         int tagId = StatsLog.WIFI_BYTES_TRANSFER;
-        PullAtomMetadata metaData = PullAtomMetadata.newBuilder()
-                .setAdditiveFields(new int[] {2, 3, 4, 5}).build();
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5})
+                .build();
         mStatsManager.registerPullAtomCallback(
                 tagId,
-                metaData,
+                metadata,
                 (atomTag, data) -> pullWifiBytesTransfer(atomTag, data),
                 Executors.newSingleThreadExecutor()
         );
@@ -356,6 +363,7 @@
             addNetworkStats(atomTag, pulledData, stats, false);
         } catch (RemoteException e) {
             Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+            return StatsManager.PULL_SKIP;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -382,36 +390,224 @@
         }
     }
 
-    private void registerWifiBytesTransferBackground() {
-        // No op.
+    /**
+     * Allows rollups per UID but keeping the set (foreground/background) slicing.
+     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+     */
+    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        entry.iface = NetworkStats.IFACE_ALL;
+        entry.tag = NetworkStats.TAG_NONE;
+        entry.metered = NetworkStats.METERED_ALL;
+        entry.roaming = NetworkStats.ROAMING_ALL;
+
+        int size = stats.size();
+        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+        for (int i = 0; i < size; i++) {
+            stats.getValues(i, recycle);
+
+            // Skip specific tags, since already counted in TAG_NONE
+            if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+            entry.set = recycle.set; // Allows slicing by background/foreground
+            entry.uid = recycle.uid;
+            entry.rxBytes = recycle.rxBytes;
+            entry.rxPackets = recycle.rxPackets;
+            entry.txBytes = recycle.txBytes;
+            entry.txPackets = recycle.txPackets;
+            // Operations purposefully omitted since we don't use them for statsd.
+            ret.combineValues(entry);
+        }
+        return ret;
     }
 
-    private void pullWifiBytesTransferBackground() {
-        // No op.
+    private void registerWifiBytesTransferBackground() {
+        int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {3, 4, 5, 6})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
+    }
+
+    private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getWifiIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            NetworkStats stats = rollupNetworkStatsByFGBG(
+                    networkStatsService.getDetailedUidStats(ifaces));
+            addNetworkStats(atomTag, pulledData, stats, true);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerMobileBytesTransfer() {
-        // No op.
+        int tagId = StatsLog.MOBILE_BYTES_TRANSFER;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullMobileBytesTransfer(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullMobileBytesTransfer() {
-        // No op.
+    private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getMobileIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            // Combine all the metrics per Uid into one record.
+            NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+            addNetworkStats(atomTag, pulledData, stats, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerMobileBytesTransferBackground() {
-        // No op.
+        int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {3, 4, 5, 6})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullMobileBytesTransferBackground() {
-        // No op.
+    private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getMobileIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            NetworkStats stats = rollupNetworkStatsByFGBG(
+                    networkStatsService.getDetailedUidStats(ifaces));
+            addNetworkStats(atomTag, pulledData, stats, true);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerBluetoothBytesTransfer() {
-        // No op.
+        int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullBluetoothBytesTransfer() {
-        // No op.
+    /**
+     * 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(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 synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
+        // TODO: Investigate whether the synchronized keyword is needed.
+        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
+                    "bluetooth");
+            adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+            return awaitControllerInfo(bluetoothReceiver);
+        } else {
+            Slog.e(TAG, "Failed to get bluetooth adapter!");
+            return null;
+        }
+    }
+
+    private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        BluetoothActivityEnergyInfo info = fetchBluetoothData();
+        if (info == null || info.getUidTraffic() == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        for (UidTraffic traffic : info.getUidTraffic()) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(traffic.getUid())
+                    .writeLong(traffic.getRxBytes())
+                    .writeLong(traffic.getTxBytes())
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerKernelWakelock() {
@@ -479,11 +675,31 @@
     }
 
     private void registerBluetoothActivityInfo() {
-        // No op.
+        int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* metadata */ null,
+                (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
     }
 
-    private void pullBluetoothActivityInfo() {
-        // No op.
+    private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+        BluetoothActivityEnergyInfo info = fetchBluetoothData();
+        if (info == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(info.getTimeStamp())
+                .writeInt(info.getBluetoothStackState())
+                .writeLong(info.getControllerTxTimeMillis())
+                .writeLong(info.getControllerRxTimeMillis())
+                .writeLong(info.getControllerIdleTimeMillis())
+                .writeLong(info.getControllerEnergyUsed())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
     }
 
     private void registerSystemElapsedRealtime() {