Merge "9/n: Add BiometricScheduler proto dump"
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index ba29a15a..fdc3d65 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -43,6 +43,7 @@
             POWER_COMPONENT_AUDIO,
             POWER_COMPONENT_VIDEO,
             POWER_COMPONENT_FLASHLIGHT,
+            POWER_COMPONENT_MOBILE_RADIO,
             POWER_COMPONENT_SYSTEM_SERVICES,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -57,8 +58,9 @@
     public static final int POWER_COMPONENT_VIDEO = 5;
     public static final int POWER_COMPONENT_FLASHLIGHT = 6;
     public static final int POWER_COMPONENT_SYSTEM_SERVICES = 7;
+    public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
 
-    public static final int POWER_COMPONENT_COUNT = 8;
+    public static final int POWER_COMPONENT_COUNT = 9;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -87,6 +89,7 @@
             TIME_COMPONENT_BLUETOOTH,
             TIME_COMPONENT_CAMERA,
             TIME_COMPONENT_FLASHLIGHT,
+            TIME_COMPONENT_MOBILE_RADIO,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface TimeComponent {
@@ -100,8 +103,9 @@
     public static final int TIME_COMPONENT_AUDIO = 5;
     public static final int TIME_COMPONENT_VIDEO = 6;
     public static final int TIME_COMPONENT_FLASHLIGHT = 7;
+    public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
 
-    public static final int TIME_COMPONENT_COUNT = 8;
+    public static final int TIME_COMPONENT_COUNT = 9;
 
     public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
index 08e358f..49bf084 100644
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ b/core/java/android/os/SystemBatteryConsumer.java
@@ -41,7 +41,7 @@
             // Reserved: APP
             DRAIN_TYPE_BLUETOOTH,
             DRAIN_TYPE_CAMERA,
-            DRAIN_TYPE_CELL,
+            DRAIN_TYPE_MOBILE_RADIO,
             DRAIN_TYPE_FLASHLIGHT,
             DRAIN_TYPE_IDLE,
             DRAIN_TYPE_MEMORY,
@@ -59,7 +59,7 @@
     public static final int DRAIN_TYPE_AMBIENT_DISPLAY = 0;
     public static final int DRAIN_TYPE_BLUETOOTH = 2;
     public static final int DRAIN_TYPE_CAMERA = 3;
-    public static final int DRAIN_TYPE_CELL = 4;
+    public static final int DRAIN_TYPE_MOBILE_RADIO = 4;
     public static final int DRAIN_TYPE_FLASHLIGHT = 5;
     public static final int DRAIN_TYPE_IDLE = 6;
     public static final int DRAIN_TYPE_MEMORY = 7;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f105320..ee3c12c 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -860,14 +860,11 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected int mScreenState = Display.STATE_UNKNOWN;
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected StopwatchTimer mScreenOnTimer;
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected StopwatchTimer mScreenDozeTimer;
+    StopwatchTimer mScreenOnTimer;
+    StopwatchTimer mScreenDozeTimer;
 
     int mScreenBrightnessBin = -1;
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected final StopwatchTimer[] mScreenBrightnessTimer =
+    final StopwatchTimer[] mScreenBrightnessTimer =
             new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
 
     boolean mPretendScreenOff;
@@ -912,8 +909,7 @@
     int mUsbDataState = USB_DATA_UNKNOWN;
 
     int mGpsSignalQualityBin = -1;
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected final StopwatchTimer[] mGpsSignalQualityTimer =
+    final StopwatchTimer[] mGpsSignalQualityTimer =
         new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
 
     int mPhoneSignalStrengthBin = -1;
@@ -929,6 +925,7 @@
 
     final LongSamplingCounter[] mNetworkByteActivityCounters =
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+
     final LongSamplingCounter[] mNetworkPacketActivityCounters =
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
 
@@ -948,8 +945,7 @@
     /**
      * The Bluetooth controller activity (time in tx, rx, idle, and power consumed) for the device.
      */
-    @VisibleForTesting
-    protected ControllerActivityCounterImpl mBluetoothActivity;
+    ControllerActivityCounterImpl mBluetoothActivity;
 
     /**
      * The Modem controller activity (time in tx, rx, idle, and power consumed) for the device.
@@ -993,8 +989,7 @@
     StopwatchTimer mWifiActiveTimer;
 
     int mBluetoothScanNesting;
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected StopwatchTimer mBluetoothScanTimer;
+    StopwatchTimer mBluetoothScanTimer;
 
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
     long mMobileRadioActiveStartTimeMs;
@@ -10718,6 +10713,31 @@
         mHandler = new MyHandler(handler.getLooper());
         mConstants = new Constants(mHandler);
         mStartCount++;
+        initTimersAndCounters();
+        mOnBattery = mOnBatteryInternal = false;
+        long uptimeUs = mClocks.uptimeMillis() * 1000;
+        long realtimeUs = mClocks.elapsedRealtime() * 1000;
+        initTimes(uptimeUs, realtimeUs);
+        mStartPlatformVersion = mEndPlatformVersion = Build.ID;
+        mDischargeStartLevel = 0;
+        mDischargeUnplugLevel = 0;
+        mDischargePlugLevel = -1;
+        mDischargeCurrentLevel = 0;
+        mCurrentBatteryLevel = 0;
+        initDischarge(realtimeUs);
+        clearHistoryLocked();
+        updateDailyDeadlineLocked();
+        mPlatformIdleStateCallback = cb;
+        mMeasuredEnergyRetriever = energyStatsCb;
+        mUserInfoProvider = userInfoProvider;
+
+        // Notify statsd that the system is initially not in doze.
+        mDeviceIdleMode = DEVICE_IDLE_MODE_OFF;
+        FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
+    }
+
+    @VisibleForTesting
+    protected void initTimersAndCounters() {
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -10789,26 +10809,6 @@
         mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
-        mOnBattery = mOnBatteryInternal = false;
-        long uptimeUs = mClocks.uptimeMillis() * 1000;
-        long realtimeUs = mClocks.elapsedRealtime() * 1000;
-        initTimes(uptimeUs, realtimeUs);
-        mStartPlatformVersion = mEndPlatformVersion = Build.ID;
-        mDischargeStartLevel = 0;
-        mDischargeUnplugLevel = 0;
-        mDischargePlugLevel = -1;
-        mDischargeCurrentLevel = 0;
-        mCurrentBatteryLevel = 0;
-        initDischarge(realtimeUs);
-        clearHistoryLocked();
-        updateDailyDeadlineLocked();
-        mPlatformIdleStateCallback = cb;
-        mMeasuredEnergyRetriever = energyStatsCb;
-        mUserInfoProvider = userInfoProvider;
-
-        // Notify statsd that the system is initially not in doze.
-        mDeviceIdleMode = DEVICE_IDLE_MODE_OFF;
-        FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
     }
 
     @UnsupportedAppUsage
@@ -11623,7 +11623,8 @@
     @GuardedBy("mModemNetworkLock")
     private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
 
-    private NetworkStats readNetworkStatsLocked(String[] ifaces) {
+    @VisibleForTesting
+    protected NetworkStats readNetworkStatsLocked(String[] ifaces) {
         try {
             if (!ArrayUtils.isEmpty(ifaces)) {
                 INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
@@ -11922,7 +11923,7 @@
     /**
      * Distribute Cell radio energy info and network traffic to apps.
      */
-    public void updateMobileRadioState(@Nullable final ModemActivityInfo activityInfo,
+    public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo,
             long elapsedRealtimeMs, long uptimeMs) {
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 790d2e5..e3bd64d 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -15,7 +15,12 @@
  */
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.telephony.CellSignalStrength;
 import android.util.Log;
@@ -26,103 +31,146 @@
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
-    private final double mPowerRadioOn;
-    private final double[] mPowerBins = new double[CellSignalStrength.getNumSignalStrengthLevels()];
-    private final double mPowerScan;
-    private BatteryStats mStats;
-    private long mTotalAppMobileActiveMs = 0;
 
-    /**
-     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
-     */
-    private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
-        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
-        final double MOBILE_POWER = mPowerRadioOn / 3600;
+    private static final int NUM_SIGNAL_STRENGTH_LEVELS =
+            CellSignalStrength.getNumSignalStrengthLevels();
 
-        final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
-                statsType);
-        final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
-                statsType);
-        final long mobileData = mobileRx + mobileTx;
+    private final UsageBasedPowerEstimator mActivePowerEstimator;
+    private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
+            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
+    private final UsageBasedPowerEstimator mScanPowerEstimator;
 
-        final long radioDataUptimeMs =
-                mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
-        final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
-                ? (mobileData / (double) radioDataUptimeMs)
-                : (((double) MOBILE_BPS) / 8 / 2048);
-        return (MOBILE_POWER / mobilePps) / (60 * 60);
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+        public long totalAppDurationMs;
+        public long signalDurationMs;
+        public long noCoverageDurationMs;
     }
 
     public MobileRadioPowerCalculator(PowerProfile profile) {
-        double temp =
+        // Power consumption when radio is active
+        double powerRadioActiveMa =
                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
-        if (temp != -1) {
-            mPowerRadioOn = temp;
-        } else {
+        if (powerRadioActiveMa == -1) {
             double sum = 0;
             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-            for (int i = 0; i < mPowerBins.length; i++) {
+            for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
                 sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
             }
-            mPowerRadioOn = sum / (mPowerBins.length + 1);
+            powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
         }
 
-        temp = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1);
-        if (temp != -1) {
-            for (int i = 0; i < mPowerBins.length; i++) {
-                mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
+        mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
+
+        // Power consumption when radio is on, but idle
+        if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+            for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+                mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
+                        profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
             }
         } else {
             double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
-            mPowerBins[0] = idle * 25 / 180;
-            for (int i = 1; i < mPowerBins.length; i++) {
-                mPowerBins[i] = Math.max(1, idle / 256);
+
+            // Magical calculations preserved for historical compatibility
+            mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
+            for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+                mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
             }
         }
 
-        mPowerScan = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0);
+        mScanPowerEstimator = new UsageBasedPowerEstimator(
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
+    }
+
+    @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+            SparseArray<UserHandle> asUsers) {
+
+        PowerAndDuration total = new PowerAndDuration();
+
+        final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED);
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            final BatteryStats.Uid uid = app.getBatteryStatsUid();
+            calculateApp(app, uid, powerPerPacketMah, total);
+        }
+
+        calculateRemaining(total, batteryStats, rawRealtimeUs);
+
+        if (total.powerMah != 0) {
+            builder.getOrCreateSystemBatteryConsumerBuilder(
+                    SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO)
+                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO,
+                            total.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, total.powerMah);
+        }
+    }
+
+    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+            double powerPerPacketMah, PowerAndDuration total) {
+        final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
+        total.totalAppDurationMs += radioActiveDurationMs;
+
+        final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs);
+
+        app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO,
+                radioActiveDurationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah);
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        mStats = batteryStats;
-        super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
+                statsType);
+        PowerAndDuration total = new PowerAndDuration();
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                final BatteryStats.Uid u = app.uidObj;
+                calculateApp(app, u, statsType, mobilePowerPerPacket, total);
+            }
+        }
 
         BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
-        calculateRemaining(radio, mStats, rawRealtimeUs, rawUptimeUs, statsType);
-        radio.sumPower();
+        calculateRemaining(total, batteryStats, rawRealtimeUs);
+        if (total.powerMah != 0) {
+            if (total.signalDurationMs != 0) {
+                radio.noCoveragePercent =
+                        total.noCoverageDurationMs * 100.0 / total.signalDurationMs;
+            }
+            radio.mobileActive = total.durationMs;
+            radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType);
+            radio.mobileRadioPowerMah = total.powerMah;
+            radio.sumPower();
+        }
         if (radio.totalPowerMah > 0) {
             sippers.add(radio);
         }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
+    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
+            double powerPerPacketMah, PowerAndDuration total) {
+        app.mobileActive = calculateDuration(u, statsType);
+        app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive);
+        total.totalAppDurationMs += app.mobileActive;
+
         // Add cost of mobile traffic.
         app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
                 statsType);
         app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
                 statsType);
-        app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
         app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
         app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
                 statsType);
         app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
                 statsType);
 
-        if (app.mobileActive > 0) {
-            // We are tracking when the radio is up, so can use the active time to
-            // determine power use.
-            mTotalAppMobileActiveMs += app.mobileActive;
-            app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000 * 60 * 60);
-        } else {
-            // We are not tracking when the radio is up, so must approximate power use
-            // based on the number of packets.
-            app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
-                    * getMobilePowerPerPacket(rawRealtimeUs, statsType);
-        }
         if (DEBUG && app.mobileRadioPowerMah != 0) {
             Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
                     + (app.mobileRxPackets + app.mobileTxPackets)
@@ -131,52 +179,80 @@
         }
     }
 
-    private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        double power = 0;
+    private long calculateDuration(BatteryStats.Uid u, int statsType) {
+        return u.getMobileRadioActiveTime(statsType) / 1000;
+    }
+
+    private double calculatePower(BatteryStats.Uid u, double powerPerPacketMah,
+            long radioActiveDurationMs) {
+        if (radioActiveDurationMs > 0) {
+            // We are tracking when the radio is up, so can use the active time to
+            // determine power use.
+            return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
+        } else {
+            // We are not tracking when the radio is up, so must approximate power use
+            // based on the number of packets.
+            final long mobileRxPackets = u.getNetworkActivityPackets(
+                    BatteryStats.NETWORK_MOBILE_RX_DATA,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            final long mobileTxPackets = u.getNetworkActivityPackets(
+                    BatteryStats.NETWORK_MOBILE_TX_DATA,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah;
+        }
+    }
+
+    private void calculateRemaining(MobileRadioPowerCalculator.PowerAndDuration total,
+            BatteryStats batteryStats, long rawRealtimeUs) {
         long signalTimeMs = 0;
-        long noCoverageTimeMs = 0;
-        for (int i = 0; i < mPowerBins.length; i++) {
-            long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
-                    / 1000;
-            final double p = (strengthTimeMs * mPowerBins[i]) / (60 * 60 * 1000);
+        double powerMah = 0;
+        for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+            long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
+            final double p = mIdlePowerEstimators[i].calculatePower(strengthTimeMs);
             if (DEBUG && p != 0) {
                 Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
                         + formatCharge(p));
             }
-            power += p;
+            powerMah += p;
             signalTimeMs += strengthTimeMs;
             if (i == 0) {
-                noCoverageTimeMs = strengthTimeMs;
+                total.noCoverageDurationMs = strengthTimeMs;
             }
         }
 
-        final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
-                / 1000;
-        final double p = (scanningTimeMs * mPowerScan) / (60 * 60 * 1000);
+        final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        final double p = mScanPowerEstimator.calculatePower(scanningTimeMs);
         if (DEBUG && p != 0) {
             Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(p));
         }
-        power += p;
-        long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
-        long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
+        powerMah += p;
+        long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
         if (remainingActiveTimeMs > 0) {
-            power += (mPowerRadioOn * remainingActiveTimeMs) / (1000 * 60 * 60);
+            powerMah += mActivePowerEstimator.calculatePower(remainingActiveTimeMs);
         }
-
-        if (power != 0) {
-            if (signalTimeMs != 0) {
-                app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
-            }
-            app.mobileActive = remainingActiveTimeMs;
-            app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
-            app.mobileRadioPowerMah = power;
-        }
+        total.durationMs = radioActiveTimeMs;
+        total.powerMah = powerMah;
+        total.signalDurationMs = signalTimeMs;
     }
 
-    @Override
-    public void reset() {
-        mTotalAppMobileActiveMs = 0;
-        mStats = null;
+    /**
+     * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
+     */
+    private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
+        final long radioDataUptimeMs =
+                stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+        final double mobilePower = mActivePowerEstimator.calculatePower(radioDataUptimeMs);
+
+        final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+        final long mobilePackets = mobileRx + mobileTx;
+
+        return mobilePackets != 0 ? mobilePower / mobilePackets : 0;
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 8ff318e..dc33253 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -58,6 +58,7 @@
         KernelWakelockReaderTest.class,
         LongSamplingCounterTest.class,
         LongSamplingCounterArrayTest.class,
+        MobileRadioPowerCalculatorTest.class,
         PowerCalculatorTest.class,
         PowerProfileTest.class,
         ScreenPowerCalculatorTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 55f64f9..59534e4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -16,10 +16,13 @@
 
 package com.android.internal.os;
 
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.net.NetworkStats;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -32,6 +35,7 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
+import org.mockito.stubbing.Answer;
 
 public class BatteryUsageStatsRule implements TestRule {
     private final PowerProfile mPowerProfile;
@@ -53,6 +57,14 @@
 
     public BatteryUsageStatsRule setAveragePower(String key, double value) {
         when(mPowerProfile.getAveragePower(key)).thenReturn(value);
+        when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setAveragePowerUnspecified(String key) {
+        when(mPowerProfile.getAveragePower(key)).thenReturn(0.0);
+        when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble()))
+                .thenAnswer((Answer<Double>) invocation -> (Double) invocation.getArguments()[1]);
         return this;
     }
 
@@ -64,6 +76,10 @@
         return this;
     }
 
+    public void setNetworkStats(NetworkStats networkStats) {
+        mBatteryStats.setNetworkStats(networkStats);
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
@@ -76,6 +92,7 @@
     }
 
     private void noteOnBattery() {
+        mBatteryStats.setOnBatteryInternal(true);
         mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
new file mode 100644
index 0000000..4230066
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.NetworkStats;
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MobileRadioPowerCalculatorTest {
+    private static final double PRECISION = 0.00001;
+    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
+            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
+            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
+            .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
+            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
+            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
+                    new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0});
+
+    @Test
+    public void testCounterBasedModel() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceType("cellular", ConnectivityManager.TYPE_MOBILE);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100);
+        mStatsRule.setNetworkStats(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[] {100, 200, 300, 400, 500}, 600);
+        stats.noteModemControllerActivity(mai, 10000, 10000);
+
+        mStatsRule.setTime(12_000_000, 12_000_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        SystemBatteryConsumer consumer =
+                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.44440);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.8);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index fc23721..bf74c8d 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.os;
 
-import android.location.GnssSignalQuality;
+import android.net.NetworkStats;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.SparseIntArray;
@@ -38,26 +38,14 @@
 public class MockBatteryStatsImpl extends BatteryStatsImpl {
     public BatteryStatsImpl.Clocks clocks;
     public boolean mForceOnBattery;
+    private NetworkStats mNetworkStats;
 
     MockBatteryStatsImpl(Clocks clocks) {
         super(clocks);
         this.clocks = mClocks;
-        mScreenOnTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
-                mOnBatteryTimeBase);
-        mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
-                mOnBatteryTimeBase);
-        for (int i = 0; i < mScreenBrightnessTimer.length; i++) {
-            mScreenBrightnessTimer[i] = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
-                    mOnBatteryTimeBase);
-        }
-        mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
-        mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, 1);
-        setExternalStatsSyncLocked(new DummyExternalStatsSync());
+        initTimersAndCounters();
 
-        for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) {
-            mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null,
-                    mOnBatteryTimeBase);
-        }
+        setExternalStatsSyncLocked(new DummyExternalStatsSync());
 
         final boolean[] supportedBuckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS];
         Arrays.fill(supportedBuckets, true);
@@ -106,6 +94,16 @@
         return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
     }
 
+    public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
+        mNetworkStats = networkStats;
+        return this;
+    }
+
+    @Override
+    protected NetworkStats readNetworkStatsLocked(String[] ifaces) {
+        return mNetworkStats;
+    }
+
     public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) {
         mPowerProfile = powerProfile;
         return this;
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index ada7eea..a3fac05 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -593,7 +593,7 @@
         }
 
         if (modemInfo != null) {
-            mStats.updateMobileRadioState(modemInfo, elapsedRealtime, uptime);
+            mStats.noteModemControllerActivity(modemInfo, elapsedRealtime, uptime);
         }
 
         if (updateFlags == UPDATE_ALL) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index cc23e233..b1cbb4a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1853,7 +1853,7 @@
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
             mHandler.post(() -> {
-                mStats.updateMobileRadioState(info, elapsedRealtime, uptime);
+                mStats.noteModemControllerActivity(info, elapsedRealtime, uptime);
             });
         }
     }