Update and expose ModemActivityInfo

Update ModemActivityInfo to present a nicer API surface and expose it as
a SystemApi. Also change clients in BatteryStats to match the new
surface.

This is part 1. Part 2 will modify the methods in TelephonyManager to
use a more up-to-standards threading model.

Bug: 170427831
Test: atest ModemActivityInfoTest
Change-Id: I762ed949342861c75d94eafce88335e7bd8c9139
diff --git a/api/system-current.txt b/api/system-current.txt
index 868d090..fceb1e1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11027,6 +11027,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 7195144..773ecd03 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -159,8 +159,6 @@
     
 MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
     
-MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
-    
 MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
     
 MissingNullability: android.telephony.SmsCbCmasInfo#toString():
diff --git a/api/test-current.txt b/api/test-current.txt
index 18320cd..4d8bdc0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4217,6 +4217,27 @@
     field public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public boolean isValid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
index 121fd33..fc17002 100644
--- a/core/java/android/os/connectivity/CellularBatteryStats.java
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -109,7 +109,7 @@
                         CellSignalStrength.getNumSignalStrengthLevels()));
         mTxTimeMs = Arrays.copyOfRange(
                 txTimeMs, 0,
-                Math.min(txTimeMs.length, ModemActivityInfo.TX_POWER_LEVELS));
+                Math.min(txTimeMs.length, ModemActivityInfo.getNumTxPowerLevels()));
         mMonitoredRailChargeConsumedMaMs = monitoredRailChargeConsumedMaMs;
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 17323ba..4c5f988 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -65,7 +65,6 @@
 import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
-import android.telephony.ModemActivityInfo.TransmitPower;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -7791,7 +7790,7 @@
         public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() {
             if (mModemControllerActivity == null) {
                 mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
-                        ModemActivityInfo.TX_POWER_LEVELS);
+                        ModemActivityInfo.getNumTxPowerLevels());
             }
             return mModemControllerActivity;
         }
@@ -9257,7 +9256,7 @@
 
             if (in.readInt() != 0) {
                 mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
-                        ModemActivityInfo.TX_POWER_LEVELS, in);
+                        ModemActivityInfo.getNumTxPowerLevels(), in);
             } else {
                 mModemControllerActivity = null;
             }
@@ -10520,7 +10519,7 @@
         mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
                 NUM_BT_TX_LEVELS);
         mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
-                ModemActivityInfo.TX_POWER_LEVELS);
+                ModemActivityInfo.getNumTxPowerLevels());
         mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase);
         mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
                 mOnBatteryTimeBase);
@@ -11710,26 +11709,7 @@
         }
     }
 
-    private ModemActivityInfo mLastModemActivityInfo =
-            new ModemActivityInfo(0, 0, 0, new int[0], 0);
-
-    private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) {
-        if (activityInfo == null) {
-            return null;
-        }
-        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
-        for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
-            txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis()
-                    - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis();
-        }
-        ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(),
-                activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(),
-                activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(),
-                txTimeMs,
-                activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis());
-        mLastModemActivityInfo = activityInfo;
-        return deltaInfo;
-    }
+    private ModemActivityInfo mLastModemActivityInfo = null;
 
     /**
      * Distribute Cell radio energy info and network traffic to apps.
@@ -11746,7 +11726,9 @@
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
         }
-        ModemActivityInfo deltaInfo = getDeltaModemActivityInfo(activityInfo);
+        ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo
+                : mLastModemActivityInfo.getDelta(activityInfo);
+        mLastModemActivityInfo = activityInfo;
 
         // Add modem tx power to history.
         addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
@@ -11778,10 +11760,9 @@
                 mModemActivity.getSleepTimeCounter().addCountLocked(
                         deltaInfo.getSleepTimeMillis());
                 mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis());
-                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
+                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
                     mModemActivity.getTxTimeCounters()[lvl]
-                        .addCountLocked(deltaInfo.getTransmitPowerInfo()
-                            .get(lvl).getTimeInMillis());
+                        .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl));
                 }
 
                 // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
@@ -11795,11 +11776,11 @@
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
                             + deltaInfo.getReceiveTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-                    List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo();
-                    for (int i = 0; i < Math.min(txPowerInfo.size(),
+                    for (int i = 0; i < Math.min(ModemActivityInfo.getNumTxPowerLevels(),
                             CellSignalStrength.getNumSignalStrengthLevels()); i++) {
-                        energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile
-                            .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+                        energyUsed += deltaInfo.getTransmitDurationMillisAtPowerLevel(i)
+                                * mPowerProfile.getAveragePower(
+                                        PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
                     }
 
                     // We store the power drain as mAms.
@@ -11894,10 +11875,10 @@
                             }
 
                             if (totalTxPackets > 0 && entry.txPackets > 0) {
-                                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
-                                    long txMs =
-                                            entry.txPackets * deltaInfo.getTransmitPowerInfo()
-                                                .get(lvl).getTimeInMillis();
+                                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels();
+                                        lvl++) {
+                                    long txMs = entry.txPackets
+                                            * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl);
                                     txMs /= totalTxPackets;
                                     activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
                                 }
@@ -11929,18 +11910,14 @@
         if (activityInfo == null) {
             return;
         }
-        List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo();
-        if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) {
-            return;
-        }
         int levelMaxTimeSpent = 0;
-        for (int i = 1; i < txPowerInfo.size(); i++) {
-            if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent)
-                .getTimeInMillis()) {
+        for (int i = 1; i < ModemActivityInfo.getNumTxPowerLevels(); i++) {
+            if (activityInfo.getTransmitDurationMillisAtPowerLevel(i)
+                    > activityInfo.getTransmitDurationMillisAtPowerLevel(levelMaxTimeSpent)) {
                 levelMaxTimeSpent = i;
             }
         }
-        if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) {
+        if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
             mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
@@ -13462,7 +13439,7 @@
             timeInRxSignalStrengthLevelMs[i] =
                 getPhoneSignalStrengthTime(i, rawRealTimeUs, which) / 1000;
         }
-        long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS,
+        long[] txTimeMs = new long[Math.min(ModemActivityInfo.getNumTxPowerLevels(),
             counter.getTxTimeCounters().length)];
         long totalTxTimeMs = 0;
         for (int i = 0; i < txTimeMs.length; i++) {
@@ -15601,7 +15578,7 @@
         mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
                 NUM_BT_TX_LEVELS, in);
         mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
-                ModemActivityInfo.TX_POWER_LEVELS, in);
+                ModemActivityInfo.getNumTxPowerLevels(), in);
         mHasWifiReporting = in.readInt() != 0;
         mHasBluetoothReporting = in.readInt() != 0;
         mHasModemReporting = in.readInt() != 0;
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index c69169e..1dabea3 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -9886,6 +9886,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e0b671f..f43a4ce 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1630,13 +1630,14 @@
             if (modemInfo == null) {
                 return StatsManager.PULL_SKIP;
             }
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, modemInfo.getTimestamp(),
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+                    modemInfo.getTimestampMillis(),
                     modemInfo.getSleepTimeMillis(), modemInfo.getIdleTimeMillis(),
-                    modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis(),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(0),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(1),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(2),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(3),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(4),
                     modemInfo.getReceiveTimeMillis(),
                     -1 /*`energy_used` field name deprecated, use -1 to indicate as unused.*/));
         } finally {
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 4fb7302..a67273c 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -227,6 +227,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index debb119..881d85c 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -16,8 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -25,46 +29,50 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
- * Reports modem activity information.
+ * Contains information about the modem's activity. May be useful for power stats reporting.
  * @hide
  */
+@SystemApi
+@TestApi
 public final class ModemActivityInfo implements Parcelable {
+    private static final int TX_POWER_LEVELS = 5;
+
     /**
-     * Tx(transmit) power level. see power index below
-     * <ul>
-     *   <li> index 0 = tx_power < 0dBm. </li>
-     *   <li> index 1 = 0dBm < tx_power < 5dBm. </li>
-     *   <li> index 2 = 5dBm < tx_power < 15dBm. </li>
-     *   <li> index 3 = 15dBm < tx_power < 20dBm. </li>
-     *   <li> index 4 = tx_power > 20dBm. </li>
-     * </ul>
-     */
-    public static final int TX_POWER_LEVELS = 5;
-    /**
-     * Tx(transmit) power level 0: tx_power < 0dBm
+     * Corresponds to transmit power of less than 0dBm.
      */
     public static final int TX_POWER_LEVEL_0 = 0;
+
     /**
-     * Tx(transmit) power level 1: 0dBm < tx_power < 5dBm
+     * Corresponds to transmit power between 0dBm and 5dBm.
      */
     public static final int TX_POWER_LEVEL_1 = 1;
+
     /**
-     * Tx(transmit) power level 2: 5dBm < tx_power < 15dBm
+     * Corresponds to transmit power between 5dBm and 15dBm.
      */
     public static final int TX_POWER_LEVEL_2 = 2;
+
     /**
-     * Tx(transmit) power level 3: 15dBm < tx_power < 20dBm.
+     * Corresponds to transmit power between 15dBm and 20dBm.
      */
     public static final int TX_POWER_LEVEL_3 = 3;
+
     /**
-     * Tx(transmit) power level 4: tx_power > 20dBm
+     * Corresponds to transmit power above 20dBm.
      */
     public static final int TX_POWER_LEVEL_4 = 4;
 
+    /**
+     * The number of transmit power levels. Fixed by HAL definition.
+     */
+    public static int getNumTxPowerLevels() {
+        return TX_POWER_LEVELS;
+    }
+
     /** @hide */
     @IntDef(prefix = {"TX_POWER_LEVEL_"}, value = {
             TX_POWER_LEVEL_0,
@@ -82,34 +90,39 @@
         new Range<>(5, 15),
         new Range<>(15, 20),
         new Range<>(20, Integer.MAX_VALUE)
-
     };
 
     private long mTimestamp;
     private int mSleepTimeMs;
     private int mIdleTimeMs;
-    private List<TransmitPower> mTransmitPowerInfo = new ArrayList<>(TX_POWER_LEVELS);
+    private int[] mTxTimeMs;
     private int mRxTimeMs;
 
+    /**
+     * @hide
+     */
+    @TestApi
     public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
                         @NonNull int[] txTimeMs, int rxTimeMs) {
+        Objects.requireNonNull(txTimeMs);
+        if (txTimeMs.length != TX_POWER_LEVELS) {
+            throw new IllegalArgumentException("txTimeMs must have length == TX_POWER_LEVELS");
+        }
         mTimestamp = timestamp;
         mSleepTimeMs = sleepTimeMs;
         mIdleTimeMs = idleTimeMs;
-        populateTransmitPowerRange(txTimeMs);
+        mTxTimeMs = txTimeMs;
         mRxTimeMs = rxTimeMs;
     }
 
-    /** helper API to populate tx power range for each bucket **/
-    private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) {
-        int i = 0;
-        for ( ; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
-            mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i]));
-        }
-        // Make sure that mTransmitPowerInfo is fully initialized.
-        for ( ; i < TX_POWER_LEVELS; i++) {
-            mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], 0));
-        }
+    /**
+     * Provided for convenience in manipulation since the API exposes long values but internal
+     * representations are ints.
+     * @hide
+     */
+    public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs,
+            @NonNull int[] txTimeMs, long rxTimeMs) {
+        this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, txTimeMs, (int) rxTimeMs);
     }
 
     @Override
@@ -118,7 +131,7 @@
             + " mTimestamp=" + mTimestamp
             + " mSleepTimeMs=" + mSleepTimeMs
             + " mIdleTimeMs=" + mIdleTimeMs
-            + " mTransmitPowerInfo[]=" + mTransmitPowerInfo.toString()
+            + " mTxTimeMs[]=" + mTxTimeMs
             + " mRxTimeMs=" + mRxTimeMs
             + "}";
     }
@@ -129,14 +142,12 @@
 
     public static final @android.annotation.NonNull Parcelable.Creator<ModemActivityInfo> CREATOR =
             new Parcelable.Creator<ModemActivityInfo>() {
-        public ModemActivityInfo createFromParcel(Parcel in) {
+        public ModemActivityInfo createFromParcel(@NonNull Parcel in) {
             long timestamp = in.readLong();
             int sleepTimeMs = in.readInt();
             int idleTimeMs = in.readInt();
             int[] txTimeMs = new int[TX_POWER_LEVELS];
-            for (int i = 0; i < TX_POWER_LEVELS; i++) {
-                txTimeMs[i] = in.readInt();
-            }
+            in.readIntArray(txTimeMs);
             int rxTimeMs = in.readInt();
             return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs,
                                 txTimeMs, rxTimeMs);
@@ -147,21 +158,25 @@
         }
     };
 
-    public void writeToParcel(Parcel dest, int flags) {
+    /**
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(mTimestamp);
         dest.writeInt(mSleepTimeMs);
         dest.writeInt(mIdleTimeMs);
-        for (int i = 0; i < TX_POWER_LEVELS; i++) {
-            dest.writeInt(mTransmitPowerInfo.get(i).getTimeInMillis());
-        }
+        dest.writeIntArray(mTxTimeMs);
         dest.writeInt(mRxTimeMs);
     }
 
     /**
-     * @return milliseconds since boot, including mTimeInMillis spent in sleep.
-     * @see SystemClock#elapsedRealtime()
+     * Gets the timestamp at which this modem activity info was recorded.
+     *
+     * @return The timestamp, as returned by {@link SystemClock#elapsedRealtime()}, when this
+     * {@link ModemActivityInfo} was recorded.
      */
-    public long getTimestamp() {
+    public @ElapsedRealtimeLong long getTimestampMillis() {
         return mTimestamp;
     }
 
@@ -171,35 +186,48 @@
     }
 
     /**
-     * @return an arrayList of {@link TransmitPower} with each element representing the total time where
-     * transmitter is awake time (in ms) for a given power range (in dbm).
+     * Gets the amount of time the modem spent transmitting at a certain power level.
      *
-     * @see #TX_POWER_LEVELS
+     * @param powerLevel The power level to query.
+     * @return The amount of time, in milliseconds, that the modem spent transmitting at the
+     * given power level.
      */
-    @NonNull
-    public List<TransmitPower> getTransmitPowerInfo() {
-        return mTransmitPowerInfo;
+    public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel(
+            @TxPowerLevel int powerLevel) {
+        return mTxTimeMs[powerLevel];
+    }
+
+    /**
+     * Gets the range of transmit powers corresponding to a certain power level.
+     *
+     * @param powerLevel The power level to query
+     * @return A {@link Range} object representing the range of intensities (in dBm) to which this
+     * power level corresponds.
+     */
+    public @NonNull Range<Integer> getTransmitPowerRange(@TxPowerLevel int powerLevel) {
+        return TX_POWER_RANGES[powerLevel];
     }
 
     /** @hide */
     public void setTransmitTimeMillis(int[] txTimeMs) {
-        populateTransmitPowerRange(txTimeMs);
-    }
-
-    /** @hide */
-    @NonNull
-    public int[] getTransmitTimeMillis() {
-        int[] transmitTimeMillis = new int[TX_POWER_LEVELS];
-        for (int i = 0; i < transmitTimeMillis.length; i++) {
-            transmitTimeMillis[i] = mTransmitPowerInfo.get(i).getTimeInMillis();
-        }
-        return transmitTimeMillis;
+        mTxTimeMs = Arrays.copyOf(txTimeMs, TX_POWER_LEVELS);
     }
 
     /**
-     * @return total mTimeInMillis (in ms) when modem is in a low power or sleep state.
+     * @return The raw array of transmit power durations
+     * @hide
      */
-    public int getSleepTimeMillis() {
+    @NonNull
+    public int[] getTransmitTimeMillis() {
+        return mTxTimeMs;
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is in a low power or sleep state.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getSleepTimeMillis() {
         return mSleepTimeMs;
     }
 
@@ -209,10 +237,44 @@
     }
 
     /**
-     * @return total mTimeInMillis (in ms) when modem is awake but neither the transmitter nor receiver are
-     * active.
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
      */
-    public int getIdleTimeMillis() {
+    public void setSleepTimeMillis(long sleepTimeMillis) {
+        mSleepTimeMs = (int) sleepTimeMillis;
+    }
+
+    /**
+     * Computes the difference between this instance of {@link ModemActivityInfo} and another
+     * instance.
+     *
+     * This method should be used to compute the amount of activity that has happened between two
+     * samples of modem activity taken at separate times. The sample passed in as an argument to
+     * this method should be the one that's taken later in time (and therefore has more activity).
+     * @param other The other instance of {@link ModemActivityInfo} to diff against.
+     * @return An instance of {@link ModemActivityInfo} representing the difference in modem
+     * activity.
+     */
+    public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) {
+        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
+        for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
+            txTimeMs[i] = other.mTxTimeMs[i] - mTxTimeMs[i];
+        }
+        return new ModemActivityInfo(other.getTimestampMillis(),
+                other.getSleepTimeMillis() - getSleepTimeMillis(),
+                other.getIdleTimeMillis() - getIdleTimeMillis(),
+                txTimeMs,
+                other.getReceiveTimeMillis() - getReceiveTimeMillis());
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is awake but neither transmitting
+     * nor receiving.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getIdleTimeMillis() {
         return mIdleTimeMs;
     }
 
@@ -222,9 +284,20 @@
     }
 
     /**
-     * @return rx(receive) mTimeInMillis in ms.
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
      */
-    public int getReceiveTimeMillis() {
+    public void setIdleTimeMillis(long idleTimeMillis) {
+        mIdleTimeMs = (int) idleTimeMillis;
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is awake and receiving data.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getReceiveTimeMillis() {
         return mRxTimeMs;
     }
 
@@ -234,71 +307,56 @@
     }
 
     /**
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
+     */
+    public void setReceiveTimeMillis(long receiveTimeMillis) {
+        mRxTimeMs = (int) receiveTimeMillis;
+    }
+
+    /**
      * Indicates if the modem has reported valid {@link ModemActivityInfo}.
      *
      * @return {@code true} if this {@link ModemActivityInfo} record is valid,
      * {@code false} otherwise.
+     *  TODO: remove usages of this outside Telephony by always returning a valid (or null) result
+     *  from telephony.
+     * @hide
      */
+    @TestApi
     public boolean isValid() {
-        for (TransmitPower powerInfo : getTransmitPowerInfo()) {
-            if(powerInfo.getTimeInMillis() < 0) {
-                return false;
-            }
-        }
+        boolean isTxPowerValid = Arrays.stream(mTxTimeMs).allMatch((i) -> i >= 0);
 
-        return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
+        return isTxPowerValid && ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
                 && (getReceiveTimeMillis() >= 0) && !isEmpty());
     }
 
     private boolean isEmpty() {
-        for (TransmitPower txVal : getTransmitPowerInfo()) {
-            if(txVal.getTimeInMillis() != 0) {
-                return false;
-            }
-        }
+        boolean isTxPowerEmpty = mTxTimeMs == null || mTxTimeMs.length == 0
+                || Arrays.stream(mTxTimeMs).allMatch((i) -> i == 0);
 
-        return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
+        return isTxPowerEmpty && ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
                 && (getReceiveTimeMillis() == 0));
     }
 
-    /**
-     * Transmit power Information, including the power range in dbm and the total time (in ms) where
-     * the transmitter is active/awake for this power range.
-     * e.g, range: 0dbm(lower) ~ 5dbm(upper)
-     *      time: 5ms
-     */
-    public class TransmitPower {
-        private int mTimeInMillis;
-        private Range<Integer> mPowerRangeInDbm;
-        /** @hide */
-        public TransmitPower(@NonNull Range<Integer> range, int time) {
-            this.mTimeInMillis = time;
-            this.mPowerRangeInDbm = range;
-        }
 
-        /**
-         * @return the total time in ms where the transmitter is active/wake for this power range
-         * {@link #getPowerRangeInDbm()}.
-         */
-        public int getTimeInMillis() {
-            return mTimeInMillis;
-        }
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ModemActivityInfo that = (ModemActivityInfo) o;
+        return mTimestamp == that.mTimestamp
+                && mSleepTimeMs == that.mSleepTimeMs
+                && mIdleTimeMs == that.mIdleTimeMs
+                && mRxTimeMs == that.mRxTimeMs
+                && Arrays.equals(mTxTimeMs, that.mTxTimeMs);
+    }
 
-        /**
-         * @return the power range in dbm. e.g, range: 0dbm(lower) ~ 5dbm(upper)
-         */
-        @NonNull
-        public Range<Integer> getPowerRangeInDbm() {
-            return mPowerRangeInDbm;
-        }
-
-        @Override
-        public String toString() {
-            return "TransmitPower{"
-                + " mTimeInMillis=" + mTimeInMillis
-                + " mPowerRangeInDbm={" + mPowerRangeInDbm.getLower()
-                + "," + mPowerRangeInDbm.getUpper()
-                + "}}";
-        }
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mTimestamp, mSleepTimeMs, mIdleTimeMs, mRxTimeMs);
+        result = 31 * result + Arrays.hashCode(mTxTimeMs);
+        return result;
     }
 }